diff --git a/.circleci/config.yml b/.circleci/config.yml index bc7053130..99795db91 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -73,15 +73,15 @@ jobs: libvips - run: - name: Install RVM and Ruby 3.3.3 + name: Install RVM and Ruby 3.4.4 command: | sudo apt-get install -y gpg gpg --keyserver hkp://keyserver.ubuntu.com --recv-keys 409B6B1796C275462A1703113804BB82D39DC0E3 7D2BAF1CF37B13E2069D6956105BD0E739499BDB \curl -sSL https://get.rvm.io | bash -s stable echo 'source ~/.rvm/scripts/rvm' >> $BASH_ENV source ~/.rvm/scripts/rvm - rvm install "3.3.3" - rvm use 3.3.3 --default + rvm install "3.4.4" + rvm use 3.4.4 --default gem install bundler -v 2.5.16 - run: diff --git a/.devcontainer/docker-compose.yml b/.devcontainer/docker-compose.yml index c5530ac17..21a9fe909 100644 --- a/.devcontainer/docker-compose.yml +++ b/.devcontainer/docker-compose.yml @@ -12,7 +12,7 @@ services: args: VARIANT: 'ubuntu-22.04' NODE_VERSION: '23.7.0' - RUBY_VERSION: '3.3.3' + RUBY_VERSION: '3.4.4' # On Linux, you may need to update USER_UID and USER_GID below if not your local UID is not 1000. USER_UID: '1000' USER_GID: '1000' @@ -25,7 +25,7 @@ services: args: VARIANT: 'ubuntu-22.04' NODE_VERSION: '23.7.0' - RUBY_VERSION: '3.3.3' + RUBY_VERSION: '3.4.4' # On Linux, you may need to update USER_UID and USER_GID below if not your local UID is not 1000. USER_UID: '1000' USER_GID: '1000' diff --git a/.rubocop.yml b/.rubocop.yml index 1cdfbc713..12e756af6 100644 --- a/.rubocop.yml +++ b/.rubocop.yml @@ -1,7 +1,10 @@ -require: +plugins: - rubocop-performance - rubocop-rails - rubocop-rspec + - rubocop-factory_bot + +require: - ./rubocop/use_from_email.rb - ./rubocop/custom_cop_location.rb @@ -13,44 +16,61 @@ Metrics/ClassLength: Exclude: - 'app/models/message.rb' - 'app/models/conversation.rb' + Metrics/MethodLength: Max: 19 + Exclude: + - 'enterprise/lib/captain/agent.rb' + RSpec/ExampleLength: Max: 25 + Style/Documentation: Enabled: false + Style/ExponentialNotation: Enabled: false + Style/FrozenStringLiteralComment: Enabled: false + Style/SymbolArray: Enabled: false + Style/OpenStructUse: Enabled: false + Style/OptionalBooleanParameter: Exclude: - 'app/services/email_templates/db_resolver_service.rb' - 'app/dispatchers/dispatcher.rb' + Style/GlobalVars: Exclude: - 'config/initializers/01_redis.rb' - 'config/initializers/rack_attack.rb' - 'lib/redis/alfred.rb' - 'lib/global_config.rb' + Style/ClassVars: Exclude: - 'app/services/email_templates/db_resolver_service.rb' + Lint/MissingSuper: Exclude: - 'app/drops/base_drop.rb' + Lint/SymbolConversion: Enabled: false + Lint/EmptyBlock: Exclude: - 'app/views/api/v1/accounts/conversations/toggle_status.json.jbuilder' + Lint/OrAssignmentToConstant: Exclude: - 'lib/redis/config.rb' + Metrics/BlockLength: Max: 30 Exclude: @@ -58,10 +78,16 @@ Metrics/BlockLength: - '**/routes.rb' - 'config/environments/*' - db/schema.rb + Metrics/ModuleLength: Exclude: - lib/seeders/message_seeder.rb - spec/support/slack_stubs.rb + +Rails/HelperInstanceVariable: + Exclude: + - enterprise/app/helpers/captain/chat_helper.rb + Rails/ApplicationController: Exclude: - 'app/controllers/api/v1/widget/messages_controller.rb' @@ -71,74 +97,101 @@ Rails/ApplicationController: - 'app/controllers/platform_controller.rb' - 'app/controllers/public_controller.rb' - 'app/controllers/survey/responses_controller.rb' + Rails/FindEach: Enabled: true Include: - 'app/**/*.rb' + Rails/CompactBlank: Enabled: false + Rails/EnvironmentVariableAccess: Enabled: false + Rails/TimeZoneAssignment: Enabled: false + Rails/RedundantPresenceValidationOnBelongsTo: Enabled: false + +Rails/InverseOf: + Exclude: + - enterprise/app/models/captain/assistant.rb + +Rails/UniqueValidationWithoutIndex: + Exclude: + - app/models/canned_response.rb + - app/models/telegram_bot.rb + - enterprise/app/models/captain_inbox.rb + - 'app/models/channel/twitter_profile.rb' + - 'app/models/webhook.rb' + - 'app/models/contact.rb' + Style/ClassAndModuleChildren: EnforcedStyle: compact Exclude: - 'config/application.rb' - 'config/initializers/monkey_patches/*' + Style/MapToHash: Enabled: false + Style/HashSyntax: Enabled: true EnforcedStyle: no_mixed_keys EnforcedShorthandSyntax: never + RSpec/NestedGroups: Enabled: true Max: 4 + RSpec/MessageSpies: Enabled: false + RSpec/StubbedMock: Enabled: false -RSpec/FactoryBot/SyntaxMethods: - Enabled: false + Naming/VariableNumber: Enabled: false + Naming/MemoizedInstanceVariableName: Exclude: - 'app/models/message.rb' + Style/GuardClause: Exclude: - 'app/builders/account_builder.rb' - 'app/models/attachment.rb' - 'app/models/message.rb' + Metrics/AbcSize: Max: 26 Exclude: - 'app/controllers/concerns/auth_helper.rb' -Rails/UniqueValidationWithoutIndex: - Exclude: - - 'app/models/channel/twitter_profile.rb' - - 'app/models/webhook.rb' - - 'app/models/contact.rb' + - 'app/models/integrations/hook.rb' - 'app/models/canned_response.rb' - 'app/models/telegram_bot.rb' + Rails/RenderInline: Exclude: - 'app/controllers/swagger_controller.rb' + Rails/ThreeStateBooleanColumn: Exclude: - 'db/migrate/20230503101201_create_sla_policies.rb' + RSpec/IndexedLet: Enabled: false + RSpec/NamedSubject: Enabled: false # we should bring this down RSpec/MultipleExpectations: Max: 7 + RSpec/MultipleMemoizedHelpers: Max: 14 @@ -166,3 +219,121 @@ AllCops: - 'tmp/**/*' - 'storage/**/*' - 'db/migrate/20230426130150_init_schema.rb' + +FactoryBot/SyntaxMethods: + Enabled: false + +# Disable new rules causing errors +Layout/LeadingCommentSpace: + Enabled: false + +Style/ReturnNilInPredicateMethodDefinition: + Enabled: false + +Style/RedundantParentheses: + Enabled: false + +Performance/StringIdentifierArgument: + Enabled: false + +Layout/EmptyLinesAroundExceptionHandlingKeywords: + Enabled: false + +Lint/LiteralAsCondition: + Enabled: false + +Style/RedundantReturn: + Enabled: false + +Layout/SpaceAroundOperators: + Enabled: false + +Rails/EnvLocal: + Enabled: false + +Rails/WhereRange: + Enabled: false + +Lint/UselessConstantScoping: + Enabled: false + +Style/MultipleComparison: + Enabled: false + +Bundler/OrderedGems: + Enabled: false + +RSpec/ExampleWording: + Enabled: false + +RSpec/ReceiveMessages: + Enabled: false + +FactoryBot/AssociationStyle: + Enabled: false + +Rails/EnumSyntax: + Enabled: false + +Lint/RedundantTypeConversion: + Enabled: false + +# Additional rules to disable +Rails/RedundantActiveRecordAllMethod: + Enabled: false + +Layout/TrailingEmptyLines: + Enabled: false + +Style/SafeNavigationChainLength: + Enabled: false + +Lint/SafeNavigationConsistency: + Enabled: false + +Lint/CopDirectiveSyntax: + Enabled: false + +# Final set of rules to disable +FactoryBot/ExcessiveCreateList: + Enabled: false + +RSpec/MissingExpectationTargetMethod: + Enabled: false + +Performance/InefficientHashSearch: + Enabled: false + +Style/RedundantSelfAssignmentBranch: + Enabled: false + +Style/YAMLFileRead: + Enabled: false + +Layout/ExtraSpacing: + Enabled: false + +Style/RedundantFilterChain: + Enabled: false + +Performance/MapMethodChain: + Enabled: false + +Rails/RootPathnameMethods: + Enabled: false + +Style/SuperArguments: + Enabled: false + +# Final remaining rules to disable +Rails/Delegate: + Enabled: false + +Style/CaseLikeIf: + Enabled: false + +FactoryBot/RedundantFactoryOption: + Enabled: false + +FactoryBot/FactoryAssociationWithStrategy: + Enabled: false \ No newline at end of file diff --git a/.ruby-version b/.ruby-version index 619b53766..f9892605c 100644 --- a/.ruby-version +++ b/.ruby-version @@ -1 +1 @@ -3.3.3 +3.4.4 diff --git a/Gemfile b/Gemfile index 1e8605379..b4752c745 100644 --- a/Gemfile +++ b/Gemfile @@ -1,10 +1,10 @@ source 'https://rubygems.org' -ruby '3.3.3' +ruby '3.4.4' ##-- base gems for rails --## gem 'rack-cors', '2.0.0', require: 'rack/cors' -gem 'rails', '~> 7.0.8.4' +gem 'rails', '~> 7.1' # Reduces boot times through caching; required in config/boot.rb gem 'bootsnap', require: false @@ -33,6 +33,8 @@ gem 'liquid' gem 'commonmarker' # Validate Data against JSON Schema gem 'json_schemer' +# used in swagger build +gem 'json_refs' # Rack middleware for blocking & throttling abusive requests gem 'rack-attack', '>= 6.7.0' # a utility tool for streaming, flexible and safe downloading of remote files @@ -196,9 +198,6 @@ group :development do gem 'scss_lint', require: false gem 'web-console', '>= 4.2.1' - # used in swagger build - gem 'json_refs' - # When we want to squash migrations gem 'squasher' @@ -237,6 +236,7 @@ group :development, :test do gem 'rubocop-performance', require: false gem 'rubocop-rails', require: false gem 'rubocop-rspec', require: false + gem 'rubocop-factory_bot', require: false gem 'seed_dump' gem 'shoulda-matchers' gem 'simplecov', '0.17.1', require: false diff --git a/Gemfile.lock b/Gemfile.lock index da19dbb72..d9908f5e1 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -25,76 +25,89 @@ GIT GEM remote: https://rubygems.org/ specs: - actioncable (7.0.8.7) - actionpack (= 7.0.8.7) - activesupport (= 7.0.8.7) + actioncable (7.1.5.1) + actionpack (= 7.1.5.1) + activesupport (= 7.1.5.1) nio4r (~> 2.0) websocket-driver (>= 0.6.1) - actionmailbox (7.0.8.7) - actionpack (= 7.0.8.7) - activejob (= 7.0.8.7) - activerecord (= 7.0.8.7) - activestorage (= 7.0.8.7) - activesupport (= 7.0.8.7) + zeitwerk (~> 2.6) + actionmailbox (7.1.5.1) + actionpack (= 7.1.5.1) + activejob (= 7.1.5.1) + activerecord (= 7.1.5.1) + activestorage (= 7.1.5.1) + activesupport (= 7.1.5.1) mail (>= 2.7.1) net-imap net-pop net-smtp - actionmailer (7.0.8.7) - actionpack (= 7.0.8.7) - actionview (= 7.0.8.7) - activejob (= 7.0.8.7) - activesupport (= 7.0.8.7) + actionmailer (7.1.5.1) + actionpack (= 7.1.5.1) + actionview (= 7.1.5.1) + activejob (= 7.1.5.1) + activesupport (= 7.1.5.1) mail (~> 2.5, >= 2.5.4) net-imap net-pop net-smtp - rails-dom-testing (~> 2.0) - actionpack (7.0.8.7) - actionview (= 7.0.8.7) - activesupport (= 7.0.8.7) - rack (~> 2.0, >= 2.2.4) + rails-dom-testing (~> 2.2) + actionpack (7.1.5.1) + actionview (= 7.1.5.1) + activesupport (= 7.1.5.1) + nokogiri (>= 1.8.5) + racc + rack (>= 2.2.4) + rack-session (>= 1.0.1) rack-test (>= 0.6.3) - rails-dom-testing (~> 2.0) - rails-html-sanitizer (~> 1.0, >= 1.2.0) - actiontext (7.0.8.7) - actionpack (= 7.0.8.7) - activerecord (= 7.0.8.7) - activestorage (= 7.0.8.7) - activesupport (= 7.0.8.7) + rails-dom-testing (~> 2.2) + rails-html-sanitizer (~> 1.6) + actiontext (7.1.5.1) + actionpack (= 7.1.5.1) + activerecord (= 7.1.5.1) + activestorage (= 7.1.5.1) + activesupport (= 7.1.5.1) globalid (>= 0.6.0) nokogiri (>= 1.8.5) - actionview (7.0.8.7) - activesupport (= 7.0.8.7) + actionview (7.1.5.1) + activesupport (= 7.1.5.1) builder (~> 3.1) - erubi (~> 1.4) - rails-dom-testing (~> 2.0) - rails-html-sanitizer (~> 1.1, >= 1.2.0) + erubi (~> 1.11) + rails-dom-testing (~> 2.2) + rails-html-sanitizer (~> 1.6) active_record_query_trace (1.8) - activejob (7.0.8.7) - activesupport (= 7.0.8.7) + activejob (7.1.5.1) + activesupport (= 7.1.5.1) globalid (>= 0.3.6) - activemodel (7.0.8.7) - activesupport (= 7.0.8.7) - activerecord (7.0.8.7) - activemodel (= 7.0.8.7) - activesupport (= 7.0.8.7) - activerecord-import (1.4.1) + activemodel (7.1.5.1) + activesupport (= 7.1.5.1) + activerecord (7.1.5.1) + activemodel (= 7.1.5.1) + activesupport (= 7.1.5.1) + timeout (>= 0.4.0) + activerecord-import (2.1.0) activerecord (>= 4.2) - activestorage (7.0.8.7) - actionpack (= 7.0.8.7) - activejob (= 7.0.8.7) - activerecord (= 7.0.8.7) - activesupport (= 7.0.8.7) + activestorage (7.1.5.1) + actionpack (= 7.1.5.1) + activejob (= 7.1.5.1) + activerecord (= 7.1.5.1) + activesupport (= 7.1.5.1) marcel (~> 1.0) - mini_mime (>= 1.1.0) - activesupport (7.0.8.7) + activesupport (7.1.5.1) + base64 + benchmark (>= 0.3) + bigdecimal concurrent-ruby (~> 1.0, >= 1.0.2) + connection_pool (>= 2.2.5) + drb i18n (>= 1.6, < 2) + logger (>= 1.4.2) minitest (>= 5.1) + mutex_m + securerandom (>= 0.3) tzinfo (~> 2.0) - acts-as-taggable-on (9.0.1) - activerecord (>= 6.0, < 7.1) + acts-as-taggable-on (12.0.0) + activerecord (>= 7.1, < 8.1) + zeitwerk (>= 2.4, < 3.0) addressable (2.8.7) public_suffix (>= 2.0.2, < 7.0) administrate (0.20.1) @@ -116,7 +129,7 @@ GEM annotate (3.2.0) activerecord (>= 3.2, < 8.0) rake (>= 10.4, < 14.0) - ast (2.4.2) + ast (2.4.3) attr_extras (7.1.0) audited (5.4.1) activerecord (>= 5.0, < 7.7) @@ -142,14 +155,15 @@ GEM statsd-ruby (~> 1.1) base64 (0.2.0) bcrypt (3.1.20) - bigdecimal (3.1.8) + benchmark (0.4.0) + bigdecimal (3.1.9) bindex (0.8.1) bootsnap (1.16.0) msgpack (~> 1.2) brakeman (5.4.1) browser (5.3.1) builder (3.3.0) - bullet (7.0.7) + bullet (8.0.7) activesupport (>= 3.0.0) uniform_notifier (~> 1.11) bundle-audit (0.1.0) @@ -161,8 +175,8 @@ GEM climate_control (1.2.0) coderay (1.1.3) commonmarker (0.23.10) - concurrent-ruby (1.3.4) - connection_pool (2.4.1) + concurrent-ruby (1.3.5) + connection_pool (2.5.3) crack (1.0.0) bigdecimal rexml @@ -176,16 +190,10 @@ GEM activerecord (>= 5.a) database_cleaner-core (~> 2.0.0) database_cleaner-core (2.0.1) - datadog-ci (0.8.3) - msgpack date (3.4.1) - ddtrace (1.23.2) - datadog-ci (~> 0.8.1) - debase-ruby_core_source (= 3.3.1) - libdatadog (~> 7.0.0.1.0) - libddwaf (~> 1.14.0.0.0) + ddtrace (0.48.0) + ffi (~> 1.0) msgpack - debase-ruby_core_source (3.3.1) debug (1.8.0) irb (>= 1.5.0) reline (>= 0.3.1) @@ -196,10 +204,10 @@ GEM railties (>= 4.1.0) responders warden (~> 1.2.3) - devise_token_auth (1.2.3) + devise_token_auth (1.2.5) bcrypt (~> 3.0) devise (> 3.5.2, < 5) - rails (>= 4.2.0, < 7.2) + rails (>= 4.2.0, < 8.1) diff-lcs (1.5.1) digest-crc (0.6.5) rake (>= 12.0.0, < 14.0.0) @@ -212,6 +220,7 @@ GEM railties (>= 6.1) down (5.4.0) addressable (~> 2.8) + drb (2.2.3) dry-cli (1.1.0) ecma-re-validator (0.4.0) regexp_parser (~> 2.2) @@ -254,7 +263,10 @@ GEM fcm (1.0.8) faraday (>= 1.0.0, < 3.0) googleauth (~> 1) - ffi (1.16.3) + ffi (1.17.2) + ffi (1.17.2-arm64-darwin) + ffi (1.17.2-x86_64-darwin) + ffi (1.17.2-x86_64-linux-gnu) ffi-compiler (1.0.1) ffi (>= 1.0.0) rake @@ -315,16 +327,13 @@ GEM google-cloud-translate-v3 (0.10.0) gapic-common (>= 0.20.0, < 2.a) google-cloud-errors (~> 1.0) - google-protobuf (3.25.5) - google-protobuf (3.25.5-arm64-darwin) - google-protobuf (3.25.5-x86_64-darwin) - google-protobuf (3.25.5-x86_64-linux) + google-protobuf (3.25.7) googleapis-common-protos (1.6.0) google-protobuf (>= 3.18, < 5.a) googleapis-common-protos-types (~> 1.7) grpc (~> 1.41) - googleapis-common-protos-types (1.14.0) - google-protobuf (~> 3.18) + googleapis-common-protos-types (1.20.0) + google-protobuf (>= 3.18, < 5.a) googleauth (1.11.2) faraday (>= 1.0, < 3.a) google-cloud-env (~> 2.1) @@ -334,17 +343,17 @@ GEM signet (>= 0.16, < 2.a) groupdate (6.2.1) activesupport (>= 5.2) - grpc (1.62.0) - google-protobuf (~> 3.25) + grpc (1.72.0) + google-protobuf (>= 3.25, < 5.0) googleapis-common-protos-types (~> 1.0) - grpc (1.62.0-arm64-darwin) - google-protobuf (~> 3.25) + grpc (1.72.0-arm64-darwin) + google-protobuf (>= 3.25, < 5.0) googleapis-common-protos-types (~> 1.0) - grpc (1.62.0-x86_64-darwin) - google-protobuf (~> 3.25) + grpc (1.72.0-x86_64-darwin) + google-protobuf (>= 3.25, < 5.0) googleapis-common-protos-types (~> 1.0) - grpc (1.62.0-x86_64-linux) - google-protobuf (~> 3.25) + grpc (1.72.0-x86_64-linux) + google-protobuf (>= 3.25, < 5.0) googleapis-common-protos-types (~> 1.0) haikunator (1.1.1) hairtrigger (1.0.0) @@ -370,7 +379,7 @@ GEM mini_mime (>= 1.0.0) multi_xml (>= 0.5.2) httpclient (2.8.3) - i18n (1.14.6) + i18n (1.14.7) concurrent-ruby (~> 1.0) image_processing (1.12.2) mini_magick (>= 4.9.5, < 5) @@ -388,7 +397,7 @@ GEM rails-dom-testing (>= 1, < 3) railties (>= 4.2.0) thor (>= 0.14, < 2.0) - json (2.6.3) + json (2.12.0) json_refs (0.1.8) hana json_schemer (0.2.24) @@ -423,21 +432,13 @@ GEM faraday-multipart json (>= 1.8) rexml + language_server-protocol (3.17.0.5) launchy (2.5.2) addressable (~> 2.8) letter_opener (1.8.1) launchy (>= 2.2, < 3) - libdatadog (7.0.0.1.0) - libdatadog (7.0.0.1.0-x86_64-linux) - libddwaf (1.14.0.0.0) - ffi (~> 1.0) - libddwaf (1.14.0.0.0-arm64-darwin) - ffi (~> 1.0) - libddwaf (1.14.0.0.0-x86_64-darwin) - ffi (~> 1.0) - libddwaf (1.14.0.0.0-x86_64-linux) - ffi (~> 1.0) line-bot-api (1.28.0) + lint_roller (1.1.0) liquid (5.4.0) listen (3.8.0) rb-fsevent (~> 0.10, >= 0.10.3) @@ -445,7 +446,7 @@ GEM llhttp-ffi (0.4.0) ffi-compiler (~> 1.0) rake (~> 13.0) - logger (1.6.0) + logger (1.7.0) lograge (0.14.0) actionpack (>= 4) activesupport (>= 4) @@ -471,10 +472,10 @@ GEM mini_magick (4.12.0) mini_mime (1.1.5) mini_portile2 (2.8.8) - minitest (5.25.4) + minitest (5.25.5) mock_redis (0.36.0) ruby2_keywords - msgpack (1.7.0) + msgpack (1.8.0) multi_json (1.15.0) multi_xml (0.6.0) multipart-post (2.3.0) @@ -545,14 +546,16 @@ GEM orm_adapter (0.5.0) os (1.1.4) ostruct (0.6.1) - parallel (1.23.0) - parser (3.2.2.1) + parallel (1.27.0) + parser (3.3.8.0) ast (~> 2.4.1) + racc pg (1.5.3) pg_search (2.3.6) activerecord (>= 5.2) activesupport (>= 5.2) pgvector (0.1.1) + prism (1.4.0) procore-sift (1.0.0) activerecord (>= 6.1) pry (0.14.2) @@ -567,7 +570,7 @@ GEM activesupport (>= 3.0.0) raabro (1.4.0) racc (1.8.1) - rack (2.2.14) + rack (2.2.15) rack-attack (6.7.0) rack (>= 1.0, < 4) rack-contrib (2.5.0) @@ -581,23 +584,28 @@ GEM rack (~> 2.2, >= 2.2.4) rack-proxy (0.7.7) rack + rack-session (1.0.2) + rack (< 3) rack-test (2.1.0) rack (>= 1.3) rack-timeout (0.6.3) - rails (7.0.8.7) - actioncable (= 7.0.8.7) - actionmailbox (= 7.0.8.7) - actionmailer (= 7.0.8.7) - actionpack (= 7.0.8.7) - actiontext (= 7.0.8.7) - actionview (= 7.0.8.7) - activejob (= 7.0.8.7) - activemodel (= 7.0.8.7) - activerecord (= 7.0.8.7) - activestorage (= 7.0.8.7) - activesupport (= 7.0.8.7) + rackup (1.0.1) + rack (< 3) + webrick + rails (7.1.5.1) + actioncable (= 7.1.5.1) + actionmailbox (= 7.1.5.1) + actionmailer (= 7.1.5.1) + actionpack (= 7.1.5.1) + actiontext (= 7.1.5.1) + actionview (= 7.1.5.1) + activejob (= 7.1.5.1) + activemodel (= 7.1.5.1) + activerecord (= 7.1.5.1) + activestorage (= 7.1.5.1) + activesupport (= 7.1.5.1) bundler (>= 1.15.0) - railties (= 7.0.8.7) + railties (= 7.1.5.1) rails-dom-testing (2.2.0) activesupport (>= 5.0.0) minitest @@ -605,13 +613,14 @@ GEM rails-html-sanitizer (1.6.1) loofah (~> 2.21) nokogiri (>= 1.15.7, != 1.16.7, != 1.16.6, != 1.16.5, != 1.16.4, != 1.16.3, != 1.16.2, != 1.16.1, != 1.16.0.rc1, != 1.16.0) - railties (7.0.8.7) - actionpack (= 7.0.8.7) - activesupport (= 7.0.8.7) - method_source + railties (7.1.5.1) + actionpack (= 7.1.5.1) + activesupport (= 7.1.5.1) + irb + rackup (>= 1.0.0) rake (>= 12.2) - thor (~> 1.0) - zeitwerk (~> 2.5) + thor (~> 1.0, >= 1.2.2) + zeitwerk (~> 2.6) rainbow (3.1.1) rake (13.2.1) rb-fsevent (0.11.2) @@ -623,7 +632,7 @@ GEM connection_pool redis-namespace (1.10.0) redis (>= 4) - regexp_parser (2.8.0) + regexp_parser (2.10.0) reline (0.3.6) io-console (~> 0.5) representable (3.2.0) @@ -643,7 +652,7 @@ GEM retriable (3.1.2) reverse_markdown (2.1.1) nokogiri - rexml (3.3.9) + rexml (3.4.1) rspec-core (3.13.0) rspec-support (~> 3.13.0) rspec-expectations (3.13.2) @@ -663,30 +672,36 @@ GEM rspec-support (3.13.1) rspec_junit_formatter (0.6.0) rspec-core (>= 2, < 4, != 2.12.0) - rubocop (1.50.2) + rubocop (1.75.6) json (~> 2.3) + language_server-protocol (~> 3.17.0.2) + lint_roller (~> 1.1.0) parallel (~> 1.10) - parser (>= 3.2.0.0) + parser (>= 3.3.0.2) rainbow (>= 2.2.2, < 4.0) - regexp_parser (>= 1.8, < 3.0) - rexml (>= 3.2.5, < 4.0) - rubocop-ast (>= 1.28.0, < 2.0) + regexp_parser (>= 2.9.3, < 3.0) + rubocop-ast (>= 1.44.0, < 2.0) ruby-progressbar (~> 1.7) - unicode-display_width (>= 2.4.0, < 3.0) - rubocop-ast (1.28.1) - parser (>= 3.2.1.0) - rubocop-capybara (2.18.0) - rubocop (~> 1.41) - rubocop-performance (1.17.1) - rubocop (>= 1.7.0, < 2.0) - rubocop-ast (>= 0.4.0) - rubocop-rails (2.19.1) + unicode-display_width (>= 2.4.0, < 4.0) + rubocop-ast (1.44.1) + parser (>= 3.3.7.2) + prism (~> 1.4) + rubocop-factory_bot (2.27.1) + lint_roller (~> 1.1) + rubocop (~> 1.72, >= 1.72.1) + rubocop-performance (1.25.0) + lint_roller (~> 1.1) + rubocop (>= 1.75.0, < 2.0) + rubocop-ast (>= 1.38.0, < 2.0) + rubocop-rails (2.32.0) activesupport (>= 4.2.0) + lint_roller (~> 1.1) rack (>= 1.1) - rubocop (>= 1.33.0, < 2.0) - rubocop-rspec (2.21.0) - rubocop (~> 1.33) - rubocop-capybara (~> 2.17) + rubocop (>= 1.75.0, < 2.0) + rubocop-ast (>= 1.44.0, < 2.0) + rubocop-rspec (3.6.0) + lint_roller (~> 1.1) + rubocop (~> 1.72, >= 1.72.1) ruby-openai (7.3.1) event_stream_parser (>= 0.3.0, < 2.0.0) faraday (>= 1) @@ -816,8 +831,10 @@ GEM unf (0.1.4) unf_ext unf_ext (0.0.8.2) - unicode-display_width (2.4.2) - uniform_notifier (1.16.0) + unicode-display_width (3.1.4) + unicode-emoji (~> 4.0, >= 4.0.4) + unicode-emoji (4.0.4) + uniform_notifier (1.17.0) uri (1.0.3) uri_template (0.7.0) valid_email2 (5.2.6) @@ -845,7 +862,9 @@ GEM addressable (>= 2.8.0) crack (>= 0.3.2) hashdiff (>= 0.4.0, < 2.0.0) - websocket-driver (0.7.6) + webrick (1.9.1) + websocket-driver (0.7.7) + base64 websocket-extensions (>= 0.1.0) websocket-extensions (0.1.5) wisper (2.0.0) @@ -951,7 +970,7 @@ DEPENDENCIES rack-cors (= 2.0.0) rack-mini-profiler (>= 3.2.0) rack-timeout - rails (~> 7.0.8.4) + rails (~> 7.1) redis redis-namespace responders (>= 3.1.1) @@ -960,6 +979,7 @@ DEPENDENCIES rspec-rails (>= 6.1.5) rspec_junit_formatter rubocop + rubocop-factory_bot rubocop-performance rubocop-rails rubocop-rspec @@ -997,7 +1017,7 @@ DEPENDENCIES working_hours RUBY VERSION - ruby 3.3.3p89 + ruby 3.4.4p34 BUNDLED WITH 2.5.16 diff --git a/app/builders/account_builder.rb b/app/builders/account_builder.rb index 6f7980586..532487a1b 100644 --- a/app/builders/account_builder.rb +++ b/app/builders/account_builder.rb @@ -32,14 +32,7 @@ class AccountBuilder end def validate_email - raise InvalidEmail.new({ domain_blocked: domain_blocked }) if domain_blocked? - - address = ValidEmail2::Address.new(@email) - if address.valid? && !address.disposable? - true - else - raise InvalidEmail.new({ valid: address.valid?, disposable: address.disposable? }) - end + Account::SignUpEmailValidationService.new(@email).perform end def validate_user @@ -81,21 +74,4 @@ class AccountBuilder @user.confirm if @confirmed @user.save! end - - def domain_blocked? - domain = @email.split('@').last - - blocked_domains.each do |blocked_domain| - return true if domain.match?(blocked_domain) - end - - false - end - - def blocked_domains - domains = GlobalConfigService.load('BLOCKED_EMAIL_DOMAINS', '') - return [] if domains.blank? - - domains.split("\n").map(&:strip) - end end diff --git a/app/builders/contact_inbox_builder.rb b/app/builders/contact_inbox_builder.rb index ffa45db2e..788ae39d1 100644 --- a/app/builders/contact_inbox_builder.rb +++ b/app/builders/contact_inbox_builder.rb @@ -59,11 +59,13 @@ class ContactInboxBuilder end def create_contact_inbox - ::ContactInbox.create_with(hmac_verified: hmac_verified || false).find_or_create_by!( + attrs = { contact_id: @contact.id, inbox_id: @inbox.id, source_id: @source_id - ) + } + + ::ContactInbox.where(attrs).first_or_create!(hmac_verified: hmac_verified || false) rescue ActiveRecord::RecordNotUnique Rails.logger.info("[ContactInboxBuilder] RecordNotUnique #{@source_id} #{@contact.id} #{@inbox.id}") update_old_contact_inbox diff --git a/app/builders/messages/instagram/base_message_builder.rb b/app/builders/messages/instagram/base_message_builder.rb index 767115bc6..8b40ba3c9 100644 --- a/app/builders/messages/instagram/base_message_builder.rb +++ b/app/builders/messages/instagram/base_message_builder.rb @@ -152,11 +152,13 @@ class Messages::Instagram::BaseMessageBuilder < Messages::Messenger::MessageBuil end def message_already_exists? - cw_message = conversation.messages.where( - source_id: @messaging[:message][:mid] - ).first + find_message_by_source_id(@messaging[:message][:mid]).present? + end - cw_message.present? + def find_message_by_source_id(source_id) + return unless source_id + + @message = Message.find_by(source_id: source_id) end def all_unsupported_files? diff --git a/app/controllers/api/v1/accounts/articles_controller.rb b/app/controllers/api/v1/accounts/articles_controller.rb index 9148e4386..da2be2312 100644 --- a/app/controllers/api/v1/accounts/articles_controller.rb +++ b/app/controllers/api/v1/accounts/articles_controller.rb @@ -68,7 +68,7 @@ class Api::V1::Accounts::ArticlesController < Api::V1::Accounts::BaseController def article_params params.require(:article).permit( - :title, :slug, :position, :content, :description, :position, :category_id, :author_id, :associated_article_id, :status, + :title, :slug, :position, :content, :description, :category_id, :author_id, :associated_article_id, :status, :locale, meta: [:title, :description, { tags: [] }] diff --git a/app/controllers/api/v1/accounts/contacts/conversations_controller.rb b/app/controllers/api/v1/accounts/contacts/conversations_controller.rb index fda19b8c2..20d66fb4d 100644 --- a/app/controllers/api/v1/accounts/contacts/conversations_controller.rb +++ b/app/controllers/api/v1/accounts/contacts/conversations_controller.rb @@ -12,10 +12,6 @@ class Api::V1::Accounts::Contacts::ConversationsController < Api::V1::Accounts:: Current.account ).perform - # Only allow conversations from inboxes the user has access to - inbox_ids = Current.user.assigned_inboxes.pluck(:id) - conversations = conversations.where(inbox_id: inbox_ids) - @conversations = conversations.order(last_activity_at: :desc).limit(20) end end diff --git a/app/controllers/api/v1/accounts/integrations/linear_controller.rb b/app/controllers/api/v1/accounts/integrations/linear_controller.rb index 4e5348e88..c66f06909 100644 --- a/app/controllers/api/v1/accounts/integrations/linear_controller.rb +++ b/app/controllers/api/v1/accounts/integrations/linear_controller.rb @@ -94,7 +94,8 @@ class Api::V1::Accounts::Integrations::LinearController < Api::V1::Accounts::Bas end def permitted_params - params.permit(:team_id, :project_id, :conversation_id, :issue_id, :link_id, :title, :description, :assignee_id, :priority, label_ids: []) + params.permit(:team_id, :project_id, :conversation_id, :issue_id, :link_id, :title, :description, :assignee_id, :priority, :state_id, + label_ids: []) end def fetch_hook diff --git a/app/controllers/api/v1/accounts/search_controller.rb b/app/controllers/api/v1/accounts/search_controller.rb index 35979f70f..13e3a6a6c 100644 --- a/app/controllers/api/v1/accounts/search_controller.rb +++ b/app/controllers/api/v1/accounts/search_controller.rb @@ -15,6 +15,10 @@ class Api::V1::Accounts::SearchController < Api::V1::Accounts::BaseController @result = search('Message') end + def articles + @result = search('Article') + end + private def search(search_type) diff --git a/app/controllers/devise_overrides/omniauth_callbacks_controller.rb b/app/controllers/devise_overrides/omniauth_callbacks_controller.rb index 2b3ea9067..db312e94f 100644 --- a/app/controllers/devise_overrides/omniauth_callbacks_controller.rb +++ b/app/controllers/devise_overrides/omniauth_callbacks_controller.rb @@ -21,7 +21,7 @@ class DeviseOverrides::OmniauthCallbacksController < DeviseTokenAuth::OmniauthCa def sign_up_user return redirect_to login_page_url(error: 'no-account-found') unless account_signup_allowed? - return redirect_to login_page_url(error: 'business-account-only') unless validate_business_account? + return redirect_to login_page_url(error: 'business-account-only') unless validate_signup_email_is_business_domain? create_account_for_user token = @resource.send(:set_reset_password_token) @@ -53,9 +53,11 @@ class DeviseOverrides::OmniauthCallbacksController < DeviseTokenAuth::OmniauthCa ).first end - def validate_business_account? - # return true if the user is a business account, false if it is a gmail account - auth_hash['info']['email'].downcase.exclude?('@gmail.com') + def validate_signup_email_is_business_domain? + # return true if the user is a business account, false if it is a blocked domain account + Account::SignUpEmailValidationService.new(auth_hash['info']['email']).perform + rescue CustomExceptions::Account::InvalidEmail + false end def create_account_for_user diff --git a/app/controllers/platform/api/v1/users_controller.rb b/app/controllers/platform/api/v1/users_controller.rb index 453e475b0..7b9284cd9 100644 --- a/app/controllers/platform/api/v1/users_controller.rb +++ b/app/controllers/platform/api/v1/users_controller.rb @@ -1,9 +1,9 @@ class Platform::Api::V1::UsersController < PlatformController # ref: https://stackoverflow.com/a/45190318/939299 # set resource is called for other actions already in platform controller - # we want to add login to that chain as well - before_action(only: [:login]) { set_resource } - before_action(only: [:login]) { validate_platform_app_permissible } + # we want to add login and token to that chain as well + before_action(only: [:login, :token]) { set_resource } + before_action(only: [:login, :token]) { validate_platform_app_permissible } def show; end @@ -18,6 +18,8 @@ class Platform::Api::V1::UsersController < PlatformController render json: { url: @resource.generate_sso_link } end + def token; end + def update @resource.assign_attributes(user_update_params) diff --git a/app/controllers/super_admin/app_configs_controller.rb b/app/controllers/super_admin/app_configs_controller.rb index 550b6c893..204bfc95b 100644 --- a/app/controllers/super_admin/app_configs_controller.rb +++ b/app/controllers/super_admin/app_configs_controller.rb @@ -32,22 +32,17 @@ class SuperAdmin::AppConfigsController < SuperAdmin::ApplicationController end def allowed_configs - @allowed_configs = case @config - when 'facebook' - %w[FB_APP_ID FB_VERIFY_TOKEN FB_APP_SECRET IG_VERIFY_TOKEN FACEBOOK_API_VERSION ENABLE_MESSENGER_CHANNEL_HUMAN_AGENT] - when 'shopify' - %w[SHOPIFY_CLIENT_ID SHOPIFY_CLIENT_SECRET] - when 'microsoft' - %w[AZURE_APP_ID AZURE_APP_SECRET] - when 'email' - ['MAILER_INBOUND_EMAIL_DOMAIN'] - when 'linear' - %w[LINEAR_CLIENT_ID LINEAR_CLIENT_SECRET] - when 'instagram' - %w[INSTAGRAM_APP_ID INSTAGRAM_APP_SECRET INSTAGRAM_VERIFY_TOKEN INSTAGRAM_API_VERSION ENABLE_INSTAGRAM_CHANNEL_HUMAN_AGENT] - else - %w[ENABLE_ACCOUNT_SIGNUP FIREBASE_PROJECT_ID FIREBASE_CREDENTIALS] - end + mapping = { + 'facebook' => %w[FB_APP_ID FB_VERIFY_TOKEN FB_APP_SECRET IG_VERIFY_TOKEN FACEBOOK_API_VERSION ENABLE_MESSENGER_CHANNEL_HUMAN_AGENT], + 'shopify' => %w[SHOPIFY_CLIENT_ID SHOPIFY_CLIENT_SECRET], + 'microsoft' => %w[AZURE_APP_ID AZURE_APP_SECRET], + 'email' => ['MAILER_INBOUND_EMAIL_DOMAIN'], + 'linear' => %w[LINEAR_CLIENT_ID LINEAR_CLIENT_SECRET], + 'slack' => %w[SLACK_CLIENT_ID SLACK_CLIENT_SECRET], + 'instagram' => %w[INSTAGRAM_APP_ID INSTAGRAM_APP_SECRET INSTAGRAM_VERIFY_TOKEN INSTAGRAM_API_VERSION ENABLE_INSTAGRAM_CHANNEL_HUMAN_AGENT] + } + + @allowed_configs = mapping.fetch(@config, %w[ENABLE_ACCOUNT_SIGNUP FIREBASE_PROJECT_ID FIREBASE_CREDENTIALS]) end end diff --git a/app/finders/conversation_finder.rb b/app/finders/conversation_finder.rb index 1694199ba..d43ed31e7 100644 --- a/app/finders/conversation_finder.rb +++ b/app/finders/conversation_finder.rb @@ -88,7 +88,10 @@ class ConversationFinder def find_conversation_by_inbox @conversations = current_account.conversations - @conversations = @conversations.where(inbox_id: @inbox_ids) unless params[:inbox_id].blank? && @is_admin + + return unless params[:inbox_id] + + @conversations = @conversations.where(inbox_id: @inbox_ids) end def find_all_conversations diff --git a/app/javascript/dashboard/api/captain/copilotMessages.js b/app/javascript/dashboard/api/captain/copilotMessages.js new file mode 100644 index 000000000..49e05398a --- /dev/null +++ b/app/javascript/dashboard/api/captain/copilotMessages.js @@ -0,0 +1,18 @@ +/* global axios */ +import ApiClient from '../ApiClient'; + +class CopilotMessages extends ApiClient { + constructor() { + super('captain/copilot_threads', { accountScoped: true }); + } + + get(threadId) { + return axios.get(`${this.url}/${threadId}/copilot_messages`); + } + + create({ threadId, ...rest }) { + return axios.post(`${this.url}/${threadId}/copilot_messages`, rest); + } +} + +export default new CopilotMessages(); diff --git a/app/javascript/dashboard/api/captain/copilotThreads.js b/app/javascript/dashboard/api/captain/copilotThreads.js new file mode 100644 index 000000000..7fdce3b91 --- /dev/null +++ b/app/javascript/dashboard/api/captain/copilotThreads.js @@ -0,0 +1,9 @@ +import ApiClient from '../ApiClient'; + +class CopilotThreads extends ApiClient { + constructor() { + super('captain/copilot_threads', { accountScoped: true }); + } +} + +export default new CopilotThreads(); diff --git a/app/javascript/dashboard/api/search.js b/app/javascript/dashboard/api/search.js index 7abb584c0..d533c2f28 100644 --- a/app/javascript/dashboard/api/search.js +++ b/app/javascript/dashboard/api/search.js @@ -40,6 +40,15 @@ class SearchAPI extends ApiClient { }, }); } + + articles({ q, page = 1 }) { + return axios.get(`${this.url}/articles`, { + params: { + q, + page: page, + }, + }); + } } export default new SearchAPI(); diff --git a/app/javascript/dashboard/components-next/Contacts/ContactsCard/ContactDeleteSection.vue b/app/javascript/dashboard/components-next/Contacts/ContactsCard/ContactDeleteSection.vue new file mode 100644 index 000000000..47b779b61 --- /dev/null +++ b/app/javascript/dashboard/components-next/Contacts/ContactsCard/ContactDeleteSection.vue @@ -0,0 +1,65 @@ + + + + + + + + + + {{ t('CONTACTS_LAYOUT.CARD.DELETE_CONTACT.MESSAGE') }} + + + + + + + diff --git a/app/javascript/dashboard/components-next/Contacts/ContactsCard/ContactsCard.vue b/app/javascript/dashboard/components-next/Contacts/ContactsCard/ContactsCard.vue index a3b0bf37c..b2b0dbfa0 100644 --- a/app/javascript/dashboard/components-next/Contacts/ContactsCard/ContactsCard.vue +++ b/app/javascript/dashboard/components-next/Contacts/ContactsCard/ContactsCard.vue @@ -7,6 +7,7 @@ import ContactsForm from 'dashboard/components-next/Contacts/ContactsForm/Contac import Button from 'dashboard/components-next/button/Button.vue'; import Avatar from 'dashboard/components-next/avatar/Avatar.vue'; import Flag from 'dashboard/components-next/flag/Flag.vue'; +import ContactDeleteSection from 'dashboard/components-next/Contacts/ContactsCard/ContactDeleteSection.vue'; import countries from 'shared/constants/countries'; const props = defineProps({ @@ -149,15 +150,15 @@ const onClickViewDetails = () => emit('showContact', props.id); /> - - + emit('showContact', props.id); /> + - + diff --git a/app/javascript/dashboard/components-next/Contacts/ContactsForm/ConfirmContactDeleteDialog.vue b/app/javascript/dashboard/components-next/Contacts/ContactsForm/ConfirmContactDeleteDialog.vue index d9c0deb1b..f43a50883 100644 --- a/app/javascript/dashboard/components-next/Contacts/ContactsForm/ConfirmContactDeleteDialog.vue +++ b/app/javascript/dashboard/components-next/Contacts/ContactsForm/ConfirmContactDeleteDialog.vue @@ -47,11 +47,7 @@ defineExpose({ dialogRef }); ref="dialogRef" type="alert" :title="t('CONTACTS_LAYOUT.DETAILS.DELETE_DIALOG.TITLE')" - :description=" - t('CONTACTS_LAYOUT.DETAILS.DELETE_DIALOG.DESCRIPTION', { - contactName: props.selectedContact.name, - }) - " + :description="t('CONTACTS_LAYOUT.DETAILS.DELETE_DIALOG.DESCRIPTION')" :confirm-button-label="t('CONTACTS_LAYOUT.DETAILS.DELETE_DIALOG.CONFIRM')" @confirm="handleDialogConfirm" /> diff --git a/app/javascript/dashboard/components-next/button/Button.vue b/app/javascript/dashboard/components-next/button/Button.vue index c54bd395a..dde1d3d9a 100644 --- a/app/javascript/dashboard/components-next/button/Button.vue +++ b/app/javascript/dashboard/components-next/button/Button.vue @@ -117,7 +117,7 @@ const STYLE_CONFIG = { 'text-n-ruby-11 hover:enabled:bg-n-ruby-9/10 focus-visible:bg-n-ruby-9/10 outline-n-ruby-8', ghost: 'text-n-ruby-11 hover:enabled:bg-n-alpha-2 focus-visible:bg-n-alpha-2 outline-transparent', - link: 'text-n-ruby-9 hover:enabled:underline focus-visible:underline outline-transparent', + link: 'text-n-ruby-9 dark:text-n-ruby-11 hover:enabled:underline focus-visible:underline outline-transparent', }, amber: { solid: diff --git a/app/javascript/dashboard/components-next/captain/pageComponents/assistant/EditAssistantForm.vue b/app/javascript/dashboard/components-next/captain/pageComponents/assistant/EditAssistantForm.vue index 362950802..3134d7062 100644 --- a/app/javascript/dashboard/components-next/captain/pageComponents/assistant/EditAssistantForm.vue +++ b/app/javascript/dashboard/components-next/captain/pageComponents/assistant/EditAssistantForm.vue @@ -42,6 +42,7 @@ const initialState = { conversationFaqs: false, memories: false, }, + temperature: 1, }; const state = reactive({ ...initialState }); @@ -87,6 +88,7 @@ const updateStateFromAssistant = assistant => { conversationFaqs: config.feature_faq || false, memories: config.feature_memory || false, }; + state.temperature = config.temperature || 1; }; const handleBasicInfoUpdate = async () => { @@ -136,6 +138,7 @@ const handleInstructionsUpdate = async () => { const payload = { config: { ...props.assistant.config, + temperature: state.temperature || 1, instructions: state.instructions, }, }; @@ -212,7 +215,7 @@ watch( - + + + + {{ t('CAPTAIN.ASSISTANTS.FORM.TEMPERATURE.LABEL') }} + + + + {{ state.temperature }} + + + {{ t('CAPTAIN.ASSISTANTS.FORM.TEMPERATURE.DESCRIPTION') }} + + -import Avatar from '../avatar/Avatar.vue'; - defineProps({ message: { type: Object, required: true, }, - supportAgent: { - type: Object, - required: true, - }, }); - - - - {{ $t('CAPTAIN.COPILOT.YOU') }} - - {{ message.content }} - + + {{ $t('CAPTAIN.COPILOT.YOU') }} + + {{ message.content }} diff --git a/app/javascript/dashboard/components-next/copilot/CopilotAssistantMessage.vue b/app/javascript/dashboard/components-next/copilot/CopilotAssistantMessage.vue index 1877e1630..0a37600bf 100644 --- a/app/javascript/dashboard/components-next/copilot/CopilotAssistantMessage.vue +++ b/app/javascript/dashboard/components-next/copilot/CopilotAssistantMessage.vue @@ -9,7 +9,6 @@ import { COPILOT_EVENTS } from 'dashboard/helper/AnalyticsHelper/events'; import MessageFormatter from 'shared/helpers/MessageFormatter.js'; import Button from 'dashboard/components-next/button/Button.vue'; -import Avatar from '../avatar/Avatar.vue'; const props = defineProps({ message: { @@ -46,33 +45,25 @@ const useCopilotResponse = () => { - - + {{ $t('CAPTAIN.NAME') }} + + {{ $t('CAPTAIN.COPILOT.EMPTY_MESSAGE') }} + + - - {{ $t('CAPTAIN.NAME') }} - - {{ $t('CAPTAIN.COPILOT.EMPTY_MESSAGE') }} - - + - - - diff --git a/app/javascript/dashboard/components-next/copilot/CopilotHeader.story.vue b/app/javascript/dashboard/components-next/copilot/CopilotHeader.story.vue new file mode 100644 index 000000000..78a345093 --- /dev/null +++ b/app/javascript/dashboard/components-next/copilot/CopilotHeader.story.vue @@ -0,0 +1,21 @@ + + + + + + + + + + + + + + + + diff --git a/app/javascript/dashboard/components-next/copilot/CopilotHeader.vue b/app/javascript/dashboard/components-next/copilot/CopilotHeader.vue new file mode 100644 index 000000000..c7a8696f3 --- /dev/null +++ b/app/javascript/dashboard/components-next/copilot/CopilotHeader.vue @@ -0,0 +1,32 @@ + + + + + + + {{ $t('CAPTAIN.COPILOT.TITLE') }} + + + + + + + + diff --git a/app/javascript/dashboard/components-next/copilot/CopilotInput.vue b/app/javascript/dashboard/components-next/copilot/CopilotInput.vue index bf14945b5..c8f1a0056 100644 --- a/app/javascript/dashboard/components-next/copilot/CopilotInput.vue +++ b/app/javascript/dashboard/components-next/copilot/CopilotInput.vue @@ -13,19 +13,16 @@ const sendMessage = () => { - + diff --git a/app/javascript/dashboard/components-next/copilot/CopilotThinkingBlock.vue b/app/javascript/dashboard/components-next/copilot/CopilotThinkingBlock.vue new file mode 100644 index 000000000..59e627f8d --- /dev/null +++ b/app/javascript/dashboard/components-next/copilot/CopilotThinkingBlock.vue @@ -0,0 +1,25 @@ + + + + + + + + {{ content }} + + + + diff --git a/app/javascript/dashboard/components-next/copilot/CopilotThinkingGroup.story.vue b/app/javascript/dashboard/components-next/copilot/CopilotThinkingGroup.story.vue new file mode 100644 index 000000000..098316dbc --- /dev/null +++ b/app/javascript/dashboard/components-next/copilot/CopilotThinkingGroup.story.vue @@ -0,0 +1,34 @@ + + + + + + + + + + + + + + diff --git a/app/javascript/dashboard/components-next/copilot/CopilotThinkingGroup.vue b/app/javascript/dashboard/components-next/copilot/CopilotThinkingGroup.vue new file mode 100644 index 000000000..c34b5b35a --- /dev/null +++ b/app/javascript/dashboard/components-next/copilot/CopilotThinkingGroup.vue @@ -0,0 +1,61 @@ + + + + + + + + {{ t('CAPTAIN.COPILOT.SHOW_STEPS') }} + + {{ thinkingCount }} + + + + + + + + diff --git a/app/javascript/dashboard/components-next/message/Message.vue b/app/javascript/dashboard/components-next/message/Message.vue index cf6cc0881..98323f79a 100644 --- a/app/javascript/dashboard/components-next/message/Message.vue +++ b/app/javascript/dashboard/components-next/message/Message.vue @@ -315,11 +315,7 @@ const componentToRender = computed(() => { }); const shouldShowContextMenu = computed(() => { - return !( - props.status === MESSAGE_STATUS.FAILED || - props.status === MESSAGE_STATUS.PROGRESS || - props.contentAttributes?.isUnsupported - ); + return !props.contentAttributes?.isUnsupported; }); const isBubble = computed(() => { @@ -344,12 +340,23 @@ const contextMenuEnabledOptions = computed(() => { const hasAttachments = !!(props.attachments && props.attachments.length > 0); const isOutgoing = props.messageType === MESSAGE_TYPES.OUTGOING; + const isFailedOrProcessing = + props.status === MESSAGE_STATUS.FAILED || + props.status === MESSAGE_STATUS.PROGRESS; return { copy: hasText, - delete: hasText || hasAttachments, - cannedResponse: isOutgoing && hasText, - replyTo: !props.private && props.inboxSupportsReplyTo.outgoing, + delete: + (hasText || hasAttachments) && + !isFailedOrProcessing && + !isMessageDeleted.value, + cannedResponse: isOutgoing && hasText && !isMessageDeleted.value, + copyLink: !isFailedOrProcessing, + translate: !isFailedOrProcessing && !isMessageDeleted.value && hasText, + replyTo: + !props.private && + props.inboxSupportsReplyTo.outgoing && + !isFailedOrProcessing, }; }); @@ -499,8 +506,8 @@ provideMessageContext({ { - + {{ t(senderTranslationKey, { diff --git a/app/javascript/dashboard/components-next/sidebar/SidebarProfileMenuStatus.vue b/app/javascript/dashboard/components-next/sidebar/SidebarProfileMenuStatus.vue index 0fdf96162..fef196162 100644 --- a/app/javascript/dashboard/components-next/sidebar/SidebarProfileMenuStatus.vue +++ b/app/javascript/dashboard/components-next/sidebar/SidebarProfileMenuStatus.vue @@ -4,6 +4,7 @@ import { useMapGetter, useStore } from 'dashboard/composables/store'; import wootConstants from 'dashboard/constants/globals'; import { useAlert } from 'dashboard/composables'; import { useI18n } from 'vue-i18n'; +import { useImpersonation } from 'dashboard/composables/useImpersonation'; import { DropdownContainer, @@ -20,6 +21,8 @@ const currentUserAvailability = useMapGetter('getCurrentUserAvailability'); const currentAccountId = useMapGetter('getCurrentAccountId'); const currentUserAutoOffline = useMapGetter('getCurrentUserAutoOffline'); +const { isImpersonating } = useImpersonation(); + const { AVAILABILITY_STATUS_KEYS } = wootConstants; const statusList = computed(() => { return [ @@ -46,6 +49,10 @@ const activeStatus = computed(() => { }); function changeAvailabilityStatus(availability) { + if (isImpersonating.value) { + useAlert(t('PROFILE_SETTINGS.FORM.AVAILABILITY.IMPERSONATING_ERROR')); + return; + } try { store.dispatch('updateAvailability', { availability, diff --git a/app/javascript/dashboard/components/ChatList.vue b/app/javascript/dashboard/components/ChatList.vue index 5aaf46808..f6d114c21 100644 --- a/app/javascript/dashboard/components/ChatList.vue +++ b/app/javascript/dashboard/components/ChatList.vue @@ -8,7 +8,6 @@ import { computed, watch, onMounted, - onUnmounted, defineEmits, } from 'vue'; import { useStore } from 'vuex'; @@ -44,7 +43,7 @@ import { useSnakeCase, } from 'dashboard/composables/useTransformKeys'; import { useEmitter } from 'dashboard/composables/emitter'; -import { useEventListener, useScrollLock } from '@vueuse/core'; +import { useEventListener } from '@vueuse/core'; import { emitter } from 'shared/helpers/mitt'; @@ -87,12 +86,8 @@ const store = useStore(); const conversationListRef = ref(null); const conversationDynamicScroller = ref(null); -const conversationListScrollableElement = computed( - () => conversationDynamicScroller.value?.$el -); -const conversationListScrollLock = useScrollLock( - conversationListScrollableElement -); + +provide('contextMenuElementTarget', conversationDynamicScroller); const activeAssigneeTab = ref(wootConstants.ASSIGNEE_TYPE.ME); const activeStatus = ref(wootConstants.STATUS_TYPE.OPEN); @@ -746,7 +741,6 @@ function allSelectedConversationsStatus(status) { function onContextMenuToggle(state) { isContextMenuOpen.value = state; - conversationListScrollLock.value = state; } function toggleSelectAll(check) { @@ -770,10 +764,6 @@ onMounted(() => { } }); -onUnmounted(() => { - conversationListScrollLock.value = false; -}); - provide('selectConversation', selectConversation); provide('deSelectConversation', deSelectConversation); provide('assignAgent', onAssignAgent); diff --git a/app/javascript/dashboard/components/layout/AvailabilityStatus.vue b/app/javascript/dashboard/components/layout/AvailabilityStatus.vue index 04190c7a4..a4995aee8 100644 --- a/app/javascript/dashboard/components/layout/AvailabilityStatus.vue +++ b/app/javascript/dashboard/components/layout/AvailabilityStatus.vue @@ -1,6 +1,7 @@ @@ -65,7 +89,7 @@ onMounted(() => { class="fixed outline-none z-[9999] cursor-pointer" :style="position" tabindex="0" - @blur="emit('close')" + @blur="handleClose" > diff --git a/app/javascript/dashboard/components/widgets/conversation/ConversationBasicFilter.vue b/app/javascript/dashboard/components/widgets/conversation/ConversationBasicFilter.vue index dc434f6c7..d699923f5 100644 --- a/app/javascript/dashboard/components/widgets/conversation/ConversationBasicFilter.vue +++ b/app/javascript/dashboard/components/widgets/conversation/ConversationBasicFilter.vue @@ -38,7 +38,7 @@ const currentSortBy = computed(() => { ); }); -const chatStatusOptions = [ +const chatStatusOptions = computed(() => [ { label: t('CHAT_LIST.CHAT_STATUS_FILTER_ITEMS.open.TEXT'), value: 'open', @@ -59,9 +59,9 @@ const chatStatusOptions = [ label: t('CHAT_LIST.CHAT_STATUS_FILTER_ITEMS.all.TEXT'), value: 'all', }, -]; +]); -const chatSortOptions = [ +const chatSortOptions = computed(() => [ { label: t('CHAT_LIST.SORT_ORDER_ITEMS.last_activity_at_asc.TEXT'), value: 'last_activity_at_asc', @@ -94,15 +94,18 @@ const chatSortOptions = [ label: t('CHAT_LIST.SORT_ORDER_ITEMS.waiting_since_desc.TEXT'), value: 'waiting_since_desc', }, -]; +]); const activeChatStatusLabel = computed( () => - chatStatusOptions.find(m => m.value === chatStatusFilter.value)?.label || '' + chatStatusOptions.value.find(m => m.value === chatStatusFilter.value) + ?.label || '' ); const activeChatSortLabel = computed( - () => chatSortOptions.find(m => m.value === chatSortFilter.value)?.label || '' + () => + chatSortOptions.value.find(m => m.value === chatSortFilter.value)?.label || + '' ); const saveSelectedFilter = (type, value) => { diff --git a/app/javascript/dashboard/components/widgets/conversation/Message.vue b/app/javascript/dashboard/components/widgets/conversation/Message.vue index 253eaeaa7..bed90fd05 100644 --- a/app/javascript/dashboard/components/widgets/conversation/Message.vue +++ b/app/javascript/dashboard/components/widgets/conversation/Message.vue @@ -185,8 +185,17 @@ export default { contextMenuEnabledOptions() { return { copy: this.hasText, - delete: this.hasText || this.hasAttachments, - cannedResponse: this.isOutgoing && this.hasText, + delete: + (this.hasText || this.hasAttachments) && + !this.isMessageDeleted && + !this.isFailed, + cannedResponse: + this.isOutgoing && this.hasText && !this.isMessageDeleted, + copyLink: !this.isFailed || !this.isProcessing, + translate: + (!this.isFailed || !this.isProcessing) && + !this.isMessageDeleted && + this.hasText, replyTo: !this.data.private && this.inboxSupportsReplyTo.outgoing, }; }, @@ -328,7 +337,7 @@ export default { return !this.sender.type || this.sender.type === 'agent_bot'; }, shouldShowContextMenu() { - return !(this.isFailed || this.isPending || this.isUnsupported); + return !this.isUnsupported; }, showAvatar() { if (this.isOutgoing || this.isTemplate) { diff --git a/app/javascript/dashboard/components/widgets/conversation/MessagesView.vue b/app/javascript/dashboard/components/widgets/conversation/MessagesView.vue index 8d0c77f1b..e5e5da385 100644 --- a/app/javascript/dashboard/components/widgets/conversation/MessagesView.vue +++ b/app/javascript/dashboard/components/widgets/conversation/MessagesView.vue @@ -1,5 +1,5 @@ + + + + + + + + + + {{ title }} + + + {{ category }} + + + + {{ truncatedContent }} + + + + diff --git a/app/javascript/dashboard/modules/search/components/SearchResultArticlesList.vue b/app/javascript/dashboard/modules/search/components/SearchResultArticlesList.vue new file mode 100644 index 000000000..679e411c2 --- /dev/null +++ b/app/javascript/dashboard/modules/search/components/SearchResultArticlesList.vue @@ -0,0 +1,53 @@ + + + + + + + + + + + diff --git a/app/javascript/dashboard/modules/search/components/SearchView.vue b/app/javascript/dashboard/modules/search/components/SearchView.vue index 1b0a9e4d7..bd48a3078 100644 --- a/app/javascript/dashboard/modules/search/components/SearchView.vue +++ b/app/javascript/dashboard/modules/search/components/SearchView.vue @@ -8,6 +8,7 @@ import { ROLES, CONVERSATION_PERMISSIONS, CONTACT_PERMISSIONS, + PORTAL_PERMISSIONS, } from 'dashboard/constants/permissions.js'; import { getUserPermissions, @@ -22,6 +23,7 @@ import SearchTabs from './SearchTabs.vue'; import SearchResultConversationsList from './SearchResultConversationsList.vue'; import SearchResultMessagesList from './SearchResultMessagesList.vue'; import SearchResultContactsList from './SearchResultContactsList.vue'; +import SearchResultArticlesList from './SearchResultArticlesList.vue'; const router = useRouter(); const store = useStore(); @@ -34,6 +36,7 @@ const pages = ref({ contacts: 1, conversations: 1, messages: 1, + articles: 1, }); const currentUser = useMapGetter('getCurrentUser'); @@ -43,6 +46,7 @@ const conversationRecords = useMapGetter( 'conversationSearch/getConversationRecords' ); const messageRecords = useMapGetter('conversationSearch/getMessageRecords'); +const articleRecords = useMapGetter('conversationSearch/getArticleRecords'); const uiFlags = useMapGetter('conversationSearch/getUIFlags'); const addTypeToRecords = (records, type) => @@ -57,6 +61,9 @@ const mappedConversations = computed(() => const mappedMessages = computed(() => addTypeToRecords(messageRecords, 'message') ); +const mappedArticles = computed(() => + addTypeToRecords(articleRecords, 'article') +); const isSelectedTabAll = computed(() => selectedTab.value === 'all'); @@ -66,6 +73,7 @@ const sliceRecordsIfAllTab = items => const contacts = computed(() => sliceRecordsIfAllTab(mappedContacts)); const conversations = computed(() => sliceRecordsIfAllTab(mappedConversations)); const messages = computed(() => sliceRecordsIfAllTab(mappedMessages)); +const articles = computed(() => sliceRecordsIfAllTab(mappedArticles)); const filterByTab = tab => computed(() => selectedTab.value === tab || isSelectedTabAll.value); @@ -73,6 +81,7 @@ const filterByTab = tab => const filterContacts = filterByTab('contacts'); const filterConversations = filterByTab('conversations'); const filterMessages = filterByTab('messages'); +const filterArticles = filterByTab('articles'); const userPermissions = computed(() => getUserPermissions(currentUser.value, currentAccountId.value) @@ -80,7 +89,12 @@ const userPermissions = computed(() => const TABS_CONFIG = { all: { - permissions: [CONTACT_PERMISSIONS, ...ROLES, ...CONVERSATION_PERMISSIONS], + permissions: [ + CONTACT_PERMISSIONS, + ...ROLES, + ...CONVERSATION_PERMISSIONS, + PORTAL_PERMISSIONS, + ], count: () => null, // No count for all tab }, contacts: { @@ -95,6 +109,10 @@ const TABS_CONFIG = { permissions: [...ROLES, ...CONVERSATION_PERMISSIONS], count: () => mappedMessages.value.length, }, + articles: { + permissions: [...ROLES, PORTAL_PERMISSIONS], + count: () => mappedArticles.value.length, + }, }; const tabs = computed(() => { @@ -123,6 +141,10 @@ const totalSearchResultsCount = computed(() => { permissions: [...ROLES, ...CONVERSATION_PERMISSIONS], count: () => conversations.value.length + messages.value.length, }, + articles: { + permissions: [...ROLES, PORTAL_PERMISSIONS], + count: () => articles.value.length, + }, }; return filterItemsByPermission( permissionCounts, @@ -138,12 +160,13 @@ const activeTabIndex = computed(() => { }); const isFetchingAny = computed(() => { - const { contact, message, conversation, isFetching } = uiFlags.value; + const { contact, message, conversation, article, isFetching } = uiFlags.value; return ( isFetching || contact.isFetching || message.isFetching || - conversation.isFetching + conversation.isFetching || + article.isFetching ); }); @@ -171,6 +194,7 @@ const showLoadMore = computed(() => { contacts: mappedContacts.value, conversations: mappedConversations.value, messages: mappedMessages.value, + articles: mappedArticles.value, }[selectedTab.value]; return ( @@ -185,10 +209,11 @@ const showViewMore = computed(() => ({ conversations: mappedConversations.value?.length > 5 && isSelectedTabAll.value, messages: mappedMessages.value?.length > 5 && isSelectedTabAll.value, + articles: mappedArticles.value?.length > 5 && isSelectedTabAll.value, })); const clearSearchResult = () => { - pages.value = { contacts: 1, conversations: 1, messages: 1 }; + pages.value = { contacts: 1, conversations: 1, messages: 1, articles: 1 }; store.dispatch('conversationSearch/clearSearchResults'); }; @@ -214,6 +239,7 @@ const loadMore = () => { contacts: 'conversationSearch/contactSearch', conversations: 'conversationSearch/conversationSearch', messages: 'conversationSearch/messageSearch', + articles: 'conversationSearch/articleSearch', }; if (uiFlags.value.isFetching || selectedTab.value === 'all') return; @@ -328,6 +354,28 @@ onUnmounted(() => { /> + + + + + : uiFlags.value.isUpdating ); -const dialogTitle = computed(() => - props.type === MODAL_TYPES.CREATE +const dialogTitle = computed(() => { + if (showAccessToken.value) { + return t('AGENT_BOTS.ACCESS_TOKEN.TITLE'); + } + + return props.type === MODAL_TYPES.CREATE ? t('AGENT_BOTS.ADD.TITLE') - : t('AGENT_BOTS.EDIT.TITLE') -); + : t('AGENT_BOTS.EDIT.TITLE'); +}); + +const dialogDescription = computed(() => { + if (showAccessToken.value) { + return t('AGENT_BOTS.ACCESS_TOKEN.DESCRIPTION'); + } + return ''; +}); const confirmButtonLabel = computed(() => props.type === MODAL_TYPES.CREATE @@ -90,6 +107,13 @@ const botUrlError = computed(() => v$.value.botUrl.$error ? v$.value.botUrl.$errors[0]?.$message : '' ); +const showAccessTokenInput = computed( + () => + showAccessToken.value || + props.type === MODAL_TYPES.EDIT || + accessToken.value +); + const resetForm = () => { Object.assign(formState, { botName: '', @@ -128,6 +152,7 @@ const handleAvatarDelete = async () => { const handleSubmit = async () => { v$.value.$touch(); if (v$.value.$invalid) return; + if (showAccessToken.value) return; const botData = { name: formState.botName, @@ -144,7 +169,7 @@ const handleSubmit = async () => { ? botData : { id: props.selectedBot.id, data: botData }; - await store.dispatch( + const response = await store.dispatch( `agentBots/${isCreate ? 'create' : 'update'}`, actionPayload ); @@ -154,7 +179,21 @@ const handleSubmit = async () => { : t('AGENT_BOTS.EDIT.API.SUCCESS_MESSAGE'); useAlert(alertKey); - dialogRef.value.close(); + // Show access token after creation + if (isCreate) { + const { access_token: responseAccessToken, id } = response || {}; + + if (id && responseAccessToken) { + accessToken.value = responseAccessToken; + toggleAccessToken(true); + } else { + accessToken.value = ''; + dialogRef.value.close(); + } + } else { + dialogRef.value.close(); + } + resetForm(); } catch (error) { const errorKey = isCreate @@ -166,17 +205,43 @@ const handleSubmit = async () => { const initializeForm = () => { if (props.selectedBot && Object.keys(props.selectedBot).length) { - const { name, description, outgoing_url, thumbnail, bot_config } = - props.selectedBot; + const { + name, + description, + outgoing_url: botUrl, + thumbnail, + bot_config: botConfig, + access_token: botAccessToken, + } = props.selectedBot; formState.botName = name || ''; formState.botDescription = description || ''; - formState.botUrl = outgoing_url || bot_config?.webhook_url || ''; + formState.botUrl = botUrl || botConfig?.webhook_url || ''; formState.botAvatarUrl = thumbnail || ''; + + if (botAccessToken && props.type === MODAL_TYPES.EDIT) { + accessToken.value = botAccessToken; + } } else { resetForm(); } }; +const onCopyToken = async value => { + await copyTextToClipboard(value); + useAlert(t('COMPONENTS.CODE.COPY_SUCCESSFUL')); +}; + +const closeModal = () => { + if (!showAccessToken.value) v$.value?.$reset(); + accessToken.value = ''; + toggleAccessToken(false); +}; + +const onClickClose = () => { + closeModal(); + dialogRef.value.close(); +}; + watch(() => props.selectedBot, initializeForm, { immediate: true, deep: true }); defineExpose({ dialogRef }); @@ -187,48 +252,68 @@ defineExpose({ dialogRef }); ref="dialogRef" type="edit" :title="dialogTitle" + :description="dialogDescription" :show-cancel-button="false" :show-confirm-button="false" - @close="v$.$reset()" + @close="closeModal" > - - - {{ $t('AGENT_BOTS.FORM.AVATAR.LABEL') }} - - + + + {{ $t('AGENT_BOTS.FORM.AVATAR.LABEL') }} + + + + + + + + + - - - - - + + + {{ $t('AGENT_BOTS.ACCESS_TOKEN.TITLE') }} + + + { @@ -46,7 +47,7 @@ const onClick = () => { copilotThreadId => { + return state.records.filter( + record => record.copilot_thread?.id === Number(copilotThreadId) + ); + }, + }, + actions: mutationTypes => ({ + upsert({ commit }, data) { + commit(mutationTypes.UPSERT, data); + }, + }), +}); diff --git a/app/javascript/dashboard/store/captain/copilotThreads.js b/app/javascript/dashboard/store/captain/copilotThreads.js new file mode 100644 index 000000000..8d820f305 --- /dev/null +++ b/app/javascript/dashboard/store/captain/copilotThreads.js @@ -0,0 +1,7 @@ +import CopilotThreadsAPI from 'dashboard/api/captain/copilotThreads'; +import { createStore } from './storeFactory'; + +export default createStore({ + name: 'CopilotThreads', + API: CopilotThreadsAPI, +}); diff --git a/app/javascript/dashboard/store/captain/storeFactory.js b/app/javascript/dashboard/store/captain/storeFactory.js index a55522062..ad669f62b 100644 --- a/app/javascript/dashboard/store/captain/storeFactory.js +++ b/app/javascript/dashboard/store/captain/storeFactory.js @@ -1,5 +1,11 @@ -import { throwErrorMessage } from 'dashboard/store/utils/api'; import * as MutationHelpers from 'shared/helpers/vuex/mutationHelpers'; +import { + createRecord, + deleteRecord, + getRecords, + showRecord, + updateRecord, +} from './storeFactoryHelper'; export const generateMutationTypes = name => { const capitalizedName = name.toUpperCase(); @@ -10,6 +16,7 @@ export const generateMutationTypes = name => { EDIT: `EDIT_${capitalizedName}`, DELETE: `DELETE_${capitalizedName}`, SET_META: `SET_${capitalizedName}_META`, + UPSERT: `UPSERT_${capitalizedName}`, }; }; @@ -33,7 +40,6 @@ export const createGetters = () => ({ getMeta: state => state.meta, }); -// store/mutations.js export const createMutations = mutationTypes => ({ [mutationTypes.SET_UI_FLAG](state, data) { state.uiFlags = { @@ -51,78 +57,19 @@ export const createMutations = mutationTypes => ({ [mutationTypes.ADD]: MutationHelpers.create, [mutationTypes.EDIT]: MutationHelpers.update, [mutationTypes.DELETE]: MutationHelpers.destroy, + [mutationTypes.UPSERT]: MutationHelpers.setSingleRecord, }); -// store/actions/crud.js export const createCrudActions = (API, mutationTypes) => ({ - async get({ commit }, params = {}) { - commit(mutationTypes.SET_UI_FLAG, { fetchingList: true }); - try { - const response = await API.get(params); - commit(mutationTypes.SET, response.data.payload); - commit(mutationTypes.SET_META, response.data.meta); - return response.data.payload; - } catch (error) { - return throwErrorMessage(error); - } finally { - commit(mutationTypes.SET_UI_FLAG, { fetchingList: false }); - } - }, - - async show({ commit }, id) { - commit(mutationTypes.SET_UI_FLAG, { fetchingItem: true }); - try { - const response = await API.show(id); - commit(mutationTypes.ADD, response.data); - return response.data; - } catch (error) { - return throwErrorMessage(error); - } finally { - commit(mutationTypes.SET_UI_FLAG, { fetchingItem: false }); - } - }, - - async create({ commit }, dataObj) { - commit(mutationTypes.SET_UI_FLAG, { creatingItem: true }); - try { - const response = await API.create(dataObj); - commit(mutationTypes.ADD, response.data); - return response.data; - } catch (error) { - return throwErrorMessage(error); - } finally { - commit(mutationTypes.SET_UI_FLAG, { creatingItem: false }); - } - }, - - async update({ commit }, { id, ...updateObj }) { - commit(mutationTypes.SET_UI_FLAG, { updatingItem: true }); - try { - const response = await API.update(id, updateObj); - commit(mutationTypes.EDIT, response.data); - return response.data; - } catch (error) { - return throwErrorMessage(error); - } finally { - commit(mutationTypes.SET_UI_FLAG, { updatingItem: false }); - } - }, - - async delete({ commit }, id) { - commit(mutationTypes.SET_UI_FLAG, { deletingItem: true }); - try { - await API.delete(id); - commit(mutationTypes.DELETE, id); - return id; - } catch (error) { - return throwErrorMessage(error); - } finally { - commit(mutationTypes.SET_UI_FLAG, { deletingItem: false }); - } - }, + get: getRecords(mutationTypes, API), + show: showRecord(mutationTypes, API), + create: createRecord(mutationTypes, API), + update: updateRecord(mutationTypes, API), + delete: deleteRecord(mutationTypes, API), }); + export const createStore = options => { - const { name, API, actions } = options; + const { name, API, actions, getters } = options; const mutationTypes = generateMutationTypes(name); const customActions = actions ? actions(mutationTypes) : {}; @@ -130,7 +77,10 @@ export const createStore = options => { return { namespaced: true, state: createInitialState(), - getters: createGetters(), + getters: { + ...createGetters(), + ...(getters || {}), + }, mutations: createMutations(mutationTypes), actions: { ...createCrudActions(API, mutationTypes), diff --git a/app/javascript/dashboard/store/captain/storeFactory.spec.js b/app/javascript/dashboard/store/captain/storeFactory.spec.js new file mode 100644 index 000000000..0aec26e40 --- /dev/null +++ b/app/javascript/dashboard/store/captain/storeFactory.spec.js @@ -0,0 +1,380 @@ +import { throwErrorMessage } from 'dashboard/store/utils/api'; +import * as MutationHelpers from 'shared/helpers/vuex/mutationHelpers'; +import { + generateMutationTypes, + createInitialState, + createGetters, + createMutations, + createCrudActions, + createStore, +} from './storeFactory'; + +vi.mock('dashboard/store/utils/api', () => ({ + throwErrorMessage: vi.fn(), +})); + +vi.mock('shared/helpers/vuex/mutationHelpers', () => ({ + set: vi.fn(), + create: vi.fn(), + update: vi.fn(), + destroy: vi.fn(), + setSingleRecord: vi.fn(), +})); + +describe('storeFactory', () => { + describe('generateMutationTypes', () => { + it('generates correct mutation types with capitalized name', () => { + const result = generateMutationTypes('test'); + expect(result).toEqual({ + SET_UI_FLAG: 'SET_TEST_UI_FLAG', + SET: 'SET_TEST', + ADD: 'ADD_TEST', + EDIT: 'EDIT_TEST', + DELETE: 'DELETE_TEST', + SET_META: 'SET_TEST_META', + UPSERT: 'UPSERT_TEST', + }); + }); + }); + + describe('createInitialState', () => { + it('returns the correct initial state structure', () => { + const result = createInitialState(); + expect(result).toEqual({ + records: [], + meta: {}, + uiFlags: { + fetchingList: false, + fetchingItem: false, + creatingItem: false, + updatingItem: false, + deletingItem: false, + }, + }); + }); + }); + + describe('createGetters', () => { + it('returns getters with correct implementations', () => { + const getters = createGetters(); + + const state = { + records: [{ id: 2 }, { id: 1 }, { id: 3 }], + uiFlags: { fetchingList: true }, + meta: { totalCount: 10, page: 1 }, + }; + expect(getters.getRecords(state)).toEqual([ + { id: 3 }, + { id: 2 }, + { id: 1 }, + ]); + + expect(getters.getRecord(state)(2)).toEqual({ id: 2 }); + expect(getters.getRecord(state)(4)).toEqual({}); + + expect(getters.getUIFlags(state)).toEqual({ + fetchingList: true, + }); + + expect(getters.getMeta(state)).toEqual({ + totalCount: 10, + page: 1, + }); + }); + }); + + describe('createMutations', () => { + it('creates mutations with correct implementations', () => { + const mutationTypes = generateMutationTypes('test'); + const mutations = createMutations(mutationTypes); + + const state = { uiFlags: { fetchingList: false } }; + mutations[mutationTypes.SET_UI_FLAG](state, { fetchingList: true }); + expect(state.uiFlags).toEqual({ fetchingList: true }); + + const metaState = { meta: {} }; + mutations[mutationTypes.SET_META](metaState, { + total_count: '10', + page: '2', + }); + expect(metaState.meta).toEqual({ totalCount: 10, page: 2 }); + + expect(mutations[mutationTypes.SET]).toBe(MutationHelpers.set); + expect(mutations[mutationTypes.ADD]).toBe(MutationHelpers.create); + expect(mutations[mutationTypes.EDIT]).toBe(MutationHelpers.update); + expect(mutations[mutationTypes.DELETE]).toBe(MutationHelpers.destroy); + expect(mutations[mutationTypes.UPSERT]).toBe( + MutationHelpers.setSingleRecord + ); + }); + }); + + describe('createCrudActions', () => { + let API; + let commit; + let mutationTypes; + let actions; + + beforeEach(() => { + API = { + get: vi.fn(), + show: vi.fn(), + create: vi.fn(), + update: vi.fn(), + delete: vi.fn(), + }; + commit = vi.fn(); + mutationTypes = generateMutationTypes('test'); + actions = createCrudActions(API, mutationTypes); + }); + + describe('get action', () => { + it('handles successful API response', async () => { + const payload = [{ id: 1 }]; + const meta = { total_count: 10, page: 1 }; + API.get.mockResolvedValue({ data: { payload, meta } }); + + const result = await actions.get({ commit }); + + expect(commit).toHaveBeenCalledWith(mutationTypes.SET_UI_FLAG, { + fetchingList: true, + }); + expect(commit).toHaveBeenCalledWith(mutationTypes.SET, payload); + expect(commit).toHaveBeenCalledWith(mutationTypes.SET_META, meta); + expect(commit).toHaveBeenCalledWith(mutationTypes.SET_UI_FLAG, { + fetchingList: false, + }); + expect(result).toEqual(payload); + }); + + it('handles API error', async () => { + const error = new Error('API Error'); + API.get.mockRejectedValue(error); + throwErrorMessage.mockReturnValue('Error thrown'); + + const result = await actions.get({ commit }); + + expect(commit).toHaveBeenCalledWith(mutationTypes.SET_UI_FLAG, { + fetchingList: true, + }); + expect(throwErrorMessage).toHaveBeenCalledWith(error); + expect(commit).toHaveBeenCalledWith(mutationTypes.SET_UI_FLAG, { + fetchingList: false, + }); + expect(result).toEqual('Error thrown'); + }); + }); + + describe('show action', () => { + it('handles successful API response', async () => { + const data = { id: 1, name: 'Test' }; + API.show.mockResolvedValue({ data }); + + const result = await actions.show({ commit }, 1); + + expect(commit).toHaveBeenCalledWith(mutationTypes.SET_UI_FLAG, { + fetchingItem: true, + }); + expect(commit).toHaveBeenCalledWith(mutationTypes.ADD, data); + expect(commit).toHaveBeenCalledWith(mutationTypes.SET_UI_FLAG, { + fetchingItem: false, + }); + expect(result).toEqual(data); + }); + + it('handles API error', async () => { + const error = new Error('API Error'); + API.show.mockRejectedValue(error); + throwErrorMessage.mockReturnValue('Error thrown'); + + const result = await actions.show({ commit }, 1); + + expect(commit).toHaveBeenCalledWith(mutationTypes.SET_UI_FLAG, { + fetchingItem: true, + }); + expect(throwErrorMessage).toHaveBeenCalledWith(error); + expect(commit).toHaveBeenCalledWith(mutationTypes.SET_UI_FLAG, { + fetchingItem: false, + }); + expect(result).toEqual('Error thrown'); + }); + }); + + describe('create action', () => { + it('handles successful API response', async () => { + const data = { id: 1, name: 'Test' }; + API.create.mockResolvedValue({ data }); + + const result = await actions.create({ commit }, { name: 'Test' }); + + expect(commit).toHaveBeenCalledWith(mutationTypes.SET_UI_FLAG, { + creatingItem: true, + }); + expect(commit).toHaveBeenCalledWith(mutationTypes.UPSERT, data); + expect(commit).toHaveBeenCalledWith(mutationTypes.SET_UI_FLAG, { + creatingItem: false, + }); + expect(result).toEqual(data); + }); + + it('handles API error', async () => { + const error = new Error('API Error'); + API.create.mockRejectedValue(error); + throwErrorMessage.mockReturnValue('Error thrown'); + + const result = await actions.create({ commit }, { name: 'Test' }); + + expect(commit).toHaveBeenCalledWith(mutationTypes.SET_UI_FLAG, { + creatingItem: true, + }); + expect(throwErrorMessage).toHaveBeenCalledWith(error); + expect(commit).toHaveBeenCalledWith(mutationTypes.SET_UI_FLAG, { + creatingItem: false, + }); + expect(result).toEqual('Error thrown'); + }); + }); + + describe('update action', () => { + it('handles successful API response', async () => { + const data = { id: 1, name: 'Updated' }; + API.update.mockResolvedValue({ data }); + + const result = await actions.update( + { commit }, + { id: 1, name: 'Updated' } + ); + + expect(commit).toHaveBeenCalledWith(mutationTypes.SET_UI_FLAG, { + updatingItem: true, + }); + expect(API.update).toHaveBeenCalledWith(1, { name: 'Updated' }); + expect(commit).toHaveBeenCalledWith(mutationTypes.EDIT, data); + expect(commit).toHaveBeenCalledWith(mutationTypes.SET_UI_FLAG, { + updatingItem: false, + }); + expect(result).toEqual(data); + }); + + it('handles API error', async () => { + const error = new Error('API Error'); + API.update.mockRejectedValue(error); + throwErrorMessage.mockReturnValue('Error thrown'); + + const result = await actions.update( + { commit }, + { id: 1, name: 'Updated' } + ); + + expect(commit).toHaveBeenCalledWith(mutationTypes.SET_UI_FLAG, { + updatingItem: true, + }); + expect(throwErrorMessage).toHaveBeenCalledWith(error); + expect(commit).toHaveBeenCalledWith(mutationTypes.SET_UI_FLAG, { + updatingItem: false, + }); + expect(result).toEqual('Error thrown'); + }); + }); + + describe('delete action', () => { + it('handles successful API response', async () => { + API.delete.mockResolvedValue({}); + + const result = await actions.delete({ commit }, 1); + + expect(commit).toHaveBeenCalledWith(mutationTypes.SET_UI_FLAG, { + deletingItem: true, + }); + expect(API.delete).toHaveBeenCalledWith(1); + expect(commit).toHaveBeenCalledWith(mutationTypes.DELETE, 1); + expect(commit).toHaveBeenCalledWith(mutationTypes.SET_UI_FLAG, { + deletingItem: false, + }); + expect(result).toEqual(1); + }); + + it('handles API error', async () => { + const error = new Error('API Error'); + API.delete.mockRejectedValue(error); + throwErrorMessage.mockReturnValue('Error thrown'); + + const result = await actions.delete({ commit }, 1); + + expect(commit).toHaveBeenCalledWith(mutationTypes.SET_UI_FLAG, { + deletingItem: true, + }); + expect(throwErrorMessage).toHaveBeenCalledWith(error); + expect(commit).toHaveBeenCalledWith(mutationTypes.SET_UI_FLAG, { + deletingItem: false, + }); + expect(result).toEqual('Error thrown'); + }); + }); + }); + + describe('createStore', () => { + it('creates a complete store with default options', () => { + const API = {}; + const store = createStore({ name: 'test', API }); + + expect(store.namespaced).toBe(true); + expect(store.state).toEqual(createInitialState()); + expect(Object.keys(store.getters)).toEqual([ + 'getRecords', + 'getRecord', + 'getUIFlags', + 'getMeta', + ]); + expect(Object.keys(store.mutations)).toEqual([ + 'SET_TEST_UI_FLAG', + 'SET_TEST_META', + 'SET_TEST', + 'ADD_TEST', + 'EDIT_TEST', + 'DELETE_TEST', + 'UPSERT_TEST', + ]); + expect(Object.keys(store.actions)).toEqual([ + 'get', + 'show', + 'create', + 'update', + 'delete', + ]); + }); + + it('creates a store with custom actions and getters', () => { + const API = {}; + const customGetters = { customGetter: () => 'custom' }; + const customActions = () => ({ + customAction: () => 'custom', + }); + + const store = createStore({ + name: 'test', + API, + getters: customGetters, + actions: customActions, + }); + + expect(store.getters).toHaveProperty('customGetter'); + expect(store.actions).toHaveProperty('customAction'); + expect(Object.keys(store.getters)).toEqual([ + 'getRecords', + 'getRecord', + 'getUIFlags', + 'getMeta', + 'customGetter', + ]); + expect(Object.keys(store.actions)).toEqual([ + 'get', + 'show', + 'create', + 'update', + 'delete', + 'customAction', + ]); + }); + }); +}); diff --git a/app/javascript/dashboard/store/captain/storeFactoryHelper.js b/app/javascript/dashboard/store/captain/storeFactoryHelper.js new file mode 100644 index 000000000..7a04d2e81 --- /dev/null +++ b/app/javascript/dashboard/store/captain/storeFactoryHelper.js @@ -0,0 +1,77 @@ +import { throwErrorMessage } from 'dashboard/store/utils/api'; + +export const getRecords = + (mutationTypes, API) => + async ({ commit }, params = {}) => { + commit(mutationTypes.SET_UI_FLAG, { fetchingList: true }); + try { + const response = await API.get(params); + commit(mutationTypes.SET, response.data.payload); + commit(mutationTypes.SET_META, response.data.meta); + return response.data.payload; + } catch (error) { + return throwErrorMessage(error); + } finally { + commit(mutationTypes.SET_UI_FLAG, { fetchingList: false }); + } + }; + +export const showRecord = + (mutationTypes, API) => + async ({ commit }, id) => { + commit(mutationTypes.SET_UI_FLAG, { fetchingItem: true }); + try { + const response = await API.show(id); + commit(mutationTypes.ADD, response.data); + return response.data; + } catch (error) { + return throwErrorMessage(error); + } finally { + commit(mutationTypes.SET_UI_FLAG, { fetchingItem: false }); + } + }; + +export const createRecord = + (mutationTypes, API) => + async ({ commit }, dataObj) => { + commit(mutationTypes.SET_UI_FLAG, { creatingItem: true }); + try { + const response = await API.create(dataObj); + commit(mutationTypes.UPSERT, response.data); + return response.data; + } catch (error) { + return throwErrorMessage(error); + } finally { + commit(mutationTypes.SET_UI_FLAG, { creatingItem: false }); + } + }; + +export const updateRecord = + (mutationTypes, API) => + async ({ commit }, { id, ...updateObj }) => { + commit(mutationTypes.SET_UI_FLAG, { updatingItem: true }); + try { + const response = await API.update(id, updateObj); + commit(mutationTypes.EDIT, response.data); + return response.data; + } catch (error) { + return throwErrorMessage(error); + } finally { + commit(mutationTypes.SET_UI_FLAG, { updatingItem: false }); + } + }; + +export const deleteRecord = + (mutationTypes, API) => + async ({ commit }, id) => { + commit(mutationTypes.SET_UI_FLAG, { deletingItem: true }); + try { + await API.delete(id); + commit(mutationTypes.DELETE, id); + return id; + } catch (error) { + return throwErrorMessage(error); + } finally { + commit(mutationTypes.SET_UI_FLAG, { deletingItem: false }); + } + }; diff --git a/app/javascript/dashboard/store/index.js b/app/javascript/dashboard/store/index.js index 5daf73ae1..960285ebf 100755 --- a/app/javascript/dashboard/store/index.js +++ b/app/javascript/dashboard/store/index.js @@ -51,6 +51,9 @@ import captainDocuments from './captain/document'; import captainResponses from './captain/response'; import captainInboxes from './captain/inboxes'; import captainBulkActions from './captain/bulkActions'; +import copilotThreads from './captain/copilotThreads'; +import copilotMessages from './captain/copilotMessages'; + const plugins = []; export default createStore({ @@ -106,6 +109,8 @@ export default createStore({ captainResponses, captainInboxes, captainBulkActions, + copilotThreads, + copilotMessages, }, plugins, }); diff --git a/app/javascript/dashboard/store/modules/auth.js b/app/javascript/dashboard/store/modules/auth.js index fa49af339..b790b2a80 100644 --- a/app/javascript/dashboard/store/modules/auth.js +++ b/app/javascript/dashboard/store/modules/auth.js @@ -2,6 +2,8 @@ import types from '../mutation-types'; import authAPI from '../../api/auth'; import { setUser, clearCookiesOnLogout } from '../utils/api'; +import SessionStorage from 'shared/helpers/sessionStorage'; +import { SESSION_STORAGE_KEYS } from 'dashboard/constants/sessionStorage'; const initialState = { currentUser: { @@ -145,8 +147,15 @@ export const actions = { updateUISettings: async ({ commit }, params) => { try { commit(types.SET_CURRENT_USER_UI_SETTINGS, params); - const response = await authAPI.updateUISettings(params); - commit(types.SET_CURRENT_USER, response.data); + + const isImpersonating = SessionStorage.get( + SESSION_STORAGE_KEYS.IMPERSONATION_USER + ); + + if (!isImpersonating) { + const response = await authAPI.updateUISettings(params); + commit(types.SET_CURRENT_USER, response.data); + } } catch (error) { // Ignore error } diff --git a/app/javascript/dashboard/store/modules/conversationSearch.js b/app/javascript/dashboard/store/modules/conversationSearch.js index b4d540fbe..d1d739076 100644 --- a/app/javascript/dashboard/store/modules/conversationSearch.js +++ b/app/javascript/dashboard/store/modules/conversationSearch.js @@ -5,12 +5,14 @@ export const initialState = { contactRecords: [], conversationRecords: [], messageRecords: [], + articleRecords: [], uiFlags: { isFetching: false, isSearchCompleted: false, contact: { isFetching: false }, conversation: { isFetching: false }, message: { isFetching: false }, + article: { isFetching: false }, }, }; @@ -27,6 +29,9 @@ export const getters = { getMessageRecords(state) { return state.messageRecords; }, + getArticleRecords(state) { + return state.articleRecords; + }, getUIFlags(state) { return state.uiFlags; }, @@ -65,6 +70,7 @@ export const actions = { dispatch('contactSearch', { q }), dispatch('conversationSearch', { q }), dispatch('messageSearch', { q }), + dispatch('articleSearch', { q }), ]); } catch (error) { // Ignore error @@ -108,6 +114,17 @@ export const actions = { commit(types.MESSAGE_SEARCH_SET_UI_FLAG, { isFetching: false }); } }, + async articleSearch({ commit }, { q, page = 1 }) { + commit(types.ARTICLE_SEARCH_SET_UI_FLAG, { isFetching: true }); + try { + const { data } = await SearchAPI.articles({ q, page }); + commit(types.ARTICLE_SEARCH_SET, data.payload.articles); + } catch (error) { + // Ignore error + } finally { + commit(types.ARTICLE_SEARCH_SET_UI_FLAG, { isFetching: false }); + } + }, async clearSearchResults({ commit }) { commit(types.CLEAR_SEARCH_RESULTS); }, @@ -126,6 +143,9 @@ export const mutations = { [types.MESSAGE_SEARCH_SET](state, records) { state.messageRecords = [...state.messageRecords, ...records]; }, + [types.ARTICLE_SEARCH_SET](state, records) { + state.articleRecords = [...state.articleRecords, ...records]; + }, [types.SEARCH_CONVERSATIONS_SET_UI_FLAG](state, uiFlags) { state.uiFlags = { ...state.uiFlags, ...uiFlags }; }, @@ -141,10 +161,14 @@ export const mutations = { [types.MESSAGE_SEARCH_SET_UI_FLAG](state, uiFlags) { state.uiFlags.message = { ...state.uiFlags.message, ...uiFlags }; }, + [types.ARTICLE_SEARCH_SET_UI_FLAG](state, uiFlags) { + state.uiFlags.article = { ...state.uiFlags.article, ...uiFlags }; + }, [types.CLEAR_SEARCH_RESULTS](state) { state.contactRecords = []; state.conversationRecords = []; state.messageRecords = []; + state.articleRecords = []; }, }; diff --git a/app/javascript/dashboard/store/modules/specs/conversationSearch/actions.spec.js b/app/javascript/dashboard/store/modules/specs/conversationSearch/actions.spec.js index ebf6c0557..6ac21f84a 100644 --- a/app/javascript/dashboard/store/modules/specs/conversationSearch/actions.spec.js +++ b/app/javascript/dashboard/store/modules/specs/conversationSearch/actions.spec.js @@ -75,6 +75,7 @@ describe('#actions', () => { q: 'test', }); expect(dispatch).toHaveBeenCalledWith('messageSearch', { q: 'test' }); + expect(dispatch).toHaveBeenCalledWith('articleSearch', { q: 'test' }); }); }); @@ -150,6 +151,30 @@ describe('#actions', () => { }); }); + describe('#articleSearch', () => { + it('should handle successful article search', async () => { + axios.get.mockResolvedValue({ + data: { payload: { articles: [{ id: 1 }] } }, + }); + + await actions.articleSearch({ commit }, { q: 'test', page: 1 }); + expect(commit.mock.calls).toEqual([ + [types.ARTICLE_SEARCH_SET_UI_FLAG, { isFetching: true }], + [types.ARTICLE_SEARCH_SET, [{ id: 1 }]], + [types.ARTICLE_SEARCH_SET_UI_FLAG, { isFetching: false }], + ]); + }); + + it('should handle failed article search', async () => { + axios.get.mockRejectedValue({}); + await actions.articleSearch({ commit }, { q: 'test' }); + expect(commit.mock.calls).toEqual([ + [types.ARTICLE_SEARCH_SET_UI_FLAG, { isFetching: true }], + [types.ARTICLE_SEARCH_SET_UI_FLAG, { isFetching: false }], + ]); + }); + }); + describe('#clearSearchResults', () => { it('should commit clear search results mutation', () => { actions.clearSearchResults({ commit }); diff --git a/app/javascript/dashboard/store/modules/specs/conversationSearch/getters.spec.js b/app/javascript/dashboard/store/modules/specs/conversationSearch/getters.spec.js index ea3ca7048..efce6084a 100644 --- a/app/javascript/dashboard/store/modules/specs/conversationSearch/getters.spec.js +++ b/app/javascript/dashboard/store/modules/specs/conversationSearch/getters.spec.js @@ -37,6 +37,15 @@ describe('#getters', () => { ]); }); + it('getArticleRecords', () => { + const state = { + articleRecords: [{ id: 1, title: 'Article 1' }], + }; + expect(getters.getArticleRecords(state)).toEqual([ + { id: 1, title: 'Article 1' }, + ]); + }); + it('getUIFlags', () => { const state = { uiFlags: { @@ -45,6 +54,7 @@ describe('#getters', () => { contact: { isFetching: true }, message: { isFetching: false }, conversation: { isFetching: false }, + article: { isFetching: false }, }, }; expect(getters.getUIFlags(state)).toEqual({ @@ -53,6 +63,7 @@ describe('#getters', () => { contact: { isFetching: true }, message: { isFetching: false }, conversation: { isFetching: false }, + article: { isFetching: false }, }); }); }); diff --git a/app/javascript/dashboard/store/modules/specs/conversationSearch/mutations.spec.js b/app/javascript/dashboard/store/modules/specs/conversationSearch/mutations.spec.js index 7bef2e527..bf7e833d0 100644 --- a/app/javascript/dashboard/store/modules/specs/conversationSearch/mutations.spec.js +++ b/app/javascript/dashboard/store/modules/specs/conversationSearch/mutations.spec.js @@ -101,17 +101,39 @@ describe('#mutations', () => { }); }); + describe('#ARTICLE_SEARCH_SET', () => { + it('should append new article records to existing ones', () => { + const state = { articleRecords: [{ id: 1 }] }; + mutations[types.ARTICLE_SEARCH_SET](state, [{ id: 2 }]); + expect(state.articleRecords).toEqual([{ id: 1 }, { id: 2 }]); + }); + }); + + describe('#ARTICLE_SEARCH_SET_UI_FLAG', () => { + it('set article search UI flags correctly', () => { + const state = { + uiFlags: { + article: { isFetching: true }, + }, + }; + mutations[types.ARTICLE_SEARCH_SET_UI_FLAG](state, { isFetching: false }); + expect(state.uiFlags.article).toEqual({ isFetching: false }); + }); + }); + describe('#CLEAR_SEARCH_RESULTS', () => { it('should clear all search records', () => { const state = { contactRecords: [{ id: 1 }], conversationRecords: [{ id: 1 }], messageRecords: [{ id: 1 }], + articleRecords: [{ id: 1 }], }; mutations[types.CLEAR_SEARCH_RESULTS](state); expect(state.contactRecords).toEqual([]); expect(state.conversationRecords).toEqual([]); expect(state.messageRecords).toEqual([]); + expect(state.articleRecords).toEqual([]); }); }); }); diff --git a/app/javascript/dashboard/store/mutation-types.js b/app/javascript/dashboard/store/mutation-types.js index a74207e92..f3817c45a 100644 --- a/app/javascript/dashboard/store/mutation-types.js +++ b/app/javascript/dashboard/store/mutation-types.js @@ -317,8 +317,10 @@ export default { CONVERSATION_SEARCH_SET: 'CONVERSATION_SEARCH_SET', CONVERSATION_SEARCH_SET_UI_FLAG: 'CONVERSATION_SEARCH_SET_UI_FLAG', MESSAGE_SEARCH_SET: 'MESSAGE_SEARCH_SET', + ARTICLE_SEARCH_SET: 'ARTICLE_SEARCH_SET', CLEAR_SEARCH_RESULTS: 'CLEAR_SEARCH_RESULTS', MESSAGE_SEARCH_SET_UI_FLAG: 'MESSAGE_SEARCH_SET_UI_FLAG', + ARTICLE_SEARCH_SET_UI_FLAG: 'ARTICLE_SEARCH_SET_UI_FLAG', FULL_SEARCH_SET_UI_FLAG: 'FULL_SEARCH_SET_UI_FLAG', SET_CONVERSATION_PARTICIPANTS_UI_FLAG: 'SET_CONVERSATION_PARTICIPANTS_UI_FLAG', diff --git a/app/javascript/dashboard/store/utils/api.js b/app/javascript/dashboard/store/utils/api.js index 281b911b5..470d98df7 100644 --- a/app/javascript/dashboard/store/utils/api.js +++ b/app/javascript/dashboard/store/utils/api.js @@ -2,7 +2,9 @@ import fromUnixTime from 'date-fns/fromUnixTime'; import differenceInDays from 'date-fns/differenceInDays'; import Cookies from 'js-cookie'; import { LOCAL_STORAGE_KEYS } from 'dashboard/constants/localStorage'; +import { SESSION_STORAGE_KEYS } from 'dashboard/constants/sessionStorage'; import { LocalStorage } from 'shared/helpers/localStorage'; +import SessionStorage from 'shared/helpers/sessionStorage'; import { emitter } from 'shared/helpers/mitt'; import { ANALYTICS_IDENTITY, @@ -44,6 +46,10 @@ export const clearLocalStorageOnLogout = () => { LocalStorage.remove(LOCAL_STORAGE_KEYS.DRAFT_MESSAGES); }; +export const clearSessionStorageOnLogout = () => { + SessionStorage.remove(SESSION_STORAGE_KEYS.IMPERSONATION_USER); +}; + export const deleteIndexedDBOnLogout = async () => { let dbs = []; try { @@ -75,6 +81,7 @@ export const clearCookiesOnLogout = () => { emitter.emit(ANALYTICS_RESET); clearBrowserSessionCookies(); clearLocalStorageOnLogout(); + clearSessionStorageOnLogout(); const globalConfig = window.globalConfig || {}; const logoutRedirectLink = globalConfig.LOGOUT_REDIRECT_LINK || '/'; window.location = logoutRedirectLink; diff --git a/app/javascript/portal/portalHelpers.js b/app/javascript/portal/portalHelpers.js index 5cced0fa4..68e2cb6a8 100644 --- a/app/javascript/portal/portalHelpers.js +++ b/app/javascript/portal/portalHelpers.js @@ -25,33 +25,71 @@ export const getHeadingsfromTheArticle = () => { return rows; }; +/** + * Converts various input formats to URL objects. + * Handles URL objects, domain strings, relative paths, and full URLs. + * @param {string|URL} input - Input to convert to URL object + * @returns {URL|null} URL object or null if input is invalid + */ +const toURL = input => { + if (!input) return null; + if (input instanceof URL) return input; + + if ( + typeof input === 'string' && + !input.includes('://') && + !input.startsWith('/') + ) { + return new URL(`https://${input}`); + } + + if (typeof input === 'string' && input.startsWith('/')) { + return new URL(input, window.location.origin); + } + + return new URL(input); +}; + +/** + * Determines if two URLs belong to the same host by comparing their normalized URL objects. + * Handles various input formats including URL objects, domain strings, relative paths, and full URLs. + * Returns false if either URL cannot be parsed or normalized. + * @param {string|URL} url1 - First URL to compare + * @param {string|URL} url2 - Second URL to compare + * @returns {boolean} True if both URLs have the same host, false otherwise + */ +const isSameHost = (url1, url2) => { + try { + const urlObj1 = toURL(url1); + const urlObj2 = toURL(url2); + + if (!urlObj1 || !urlObj2) return false; + + return urlObj1.hostname === urlObj2.hostname; + } catch (error) { + return false; + } +}; + export const openExternalLinksInNewTab = () => { const { customDomain, hostURL } = window.portalConfig; - const isSameHost = - window.location.href.includes(customDomain) || - window.location.href.includes(hostURL); - - // Modify external links only on articles page const isOnArticlePage = - isSameHost && document.querySelector('#cw-article-content') !== null; + document.querySelector('#cw-article-content') !== null; document.addEventListener('click', event => { if (!isOnArticlePage) return; - // Some of the links come wrapped in strong tag through prosemirror + const link = event.target.closest('a'); - const isTagAnchor = event.target.tagName === 'A'; - const isParentTagAnchor = - event.target.tagName === 'STRONG' && - event.target.parentNode.tagName === 'A'; - - if (isTagAnchor || isParentTagAnchor) { - const link = isTagAnchor ? event.target : event.target.parentNode; + if (link) { + const currentLocation = window.location.href; + const linkHref = link.href; + // Check against current location and custom domains const isInternalLink = - link.hostname === window.location.hostname || - link.href.includes(customDomain) || - link.href.includes(hostURL); + isSameHost(linkHref, currentLocation) || + (customDomain && isSameHost(linkHref, customDomain)) || + (hostURL && isSameHost(linkHref, hostURL)); if (!isInternalLink) { link.target = '_blank'; diff --git a/app/javascript/portal/specs/portal.spec.js b/app/javascript/portal/specs/portal.spec.js index 13edd3718..5205c5d45 100644 --- a/app/javascript/portal/specs/portal.spec.js +++ b/app/javascript/portal/specs/portal.spec.js @@ -1,6 +1,9 @@ import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest'; import { JSDOM } from 'jsdom'; -import { InitializationHelpers } from '../portalHelpers'; +import { + InitializationHelpers, + openExternalLinksInNewTab, +} from '../portalHelpers'; describe('InitializationHelpers.navigateToLocalePage', () => { let dom; @@ -44,3 +47,139 @@ describe('InitializationHelpers.navigateToLocalePage', () => { ); }); }); + +describe('openExternalLinksInNewTab', () => { + let dom; + let document; + let window; + + beforeEach(() => { + dom = new JSDOM( + ` + + + + External + Internal + Custom + CodeBold + + Visit the preferences centre here > https://external.com + + + + `, + { url: 'https://app.chatwoot.com/hc/article' } + ); + + document = dom.window.document; + window = dom.window; + + window.portalConfig = { + customDomain: 'custom.domain.com', + hostURL: 'app.chatwoot.com', + }; + + global.document = document; + global.window = window; + }); + + afterEach(() => { + dom = null; + document = null; + window = null; + delete global.document; + delete global.window; + }); + + const simulateClick = selector => { + const element = document.querySelector(selector); + const event = new window.MouseEvent('click', { bubbles: true }); + element.dispatchEvent(event); + return element.closest('a') || element; + }; + + it('opens external links in new tab', () => { + openExternalLinksInNewTab(); + + const link = simulateClick('#external'); + expect(link.target).toBe('_blank'); + expect(link.rel).toBe('noopener noreferrer'); + }); + + it('preserves internal links', () => { + openExternalLinksInNewTab(); + + const internal = simulateClick('#internal'); + const custom = simulateClick('#custom'); + + expect(internal.target).not.toBe('_blank'); + expect(custom.target).not.toBe('_blank'); + }); + + it('handles clicks on nested elements', () => { + openExternalLinksInNewTab(); + + simulateClick('#nested code'); + simulateClick('#nested strong'); + + const link = document.getElementById('nested'); + expect(link.target).toBe('_blank'); + expect(link.rel).toBe('noopener noreferrer'); + }); + + it('handles links inside list items with strong tags', () => { + openExternalLinksInNewTab(); + + // Click on the strong element inside the link in the list + simulateClick('#list-link strong'); + + const link = document.getElementById('list-link'); + expect(link.target).toBe('_blank'); + expect(link.rel).toBe('noopener noreferrer'); + }); + + it('opens external links in a new tab even if customDomain is empty', () => { + window = dom.window; + window.portalConfig = { + hostURL: 'app.chatwoot.com', + }; + + global.window = window; + + openExternalLinksInNewTab(); + + const link = simulateClick('#external'); + const internal = simulateClick('#internal'); + const custom = simulateClick('#custom'); + + expect(link.target).toBe('_blank'); + expect(link.rel).toBe('noopener noreferrer'); + + expect(internal.target).not.toBe('_blank'); + // this will be blank since the configs customDomain is empty + // which is a fair expectation + expect(custom.target).toBe('_blank'); + }); + + it('opens external links in a new tab even if hostURL is empty', () => { + window = dom.window; + window.portalConfig = { + customDomain: 'custom.domain.com', + }; + + global.window = window; + + openExternalLinksInNewTab(); + + const link = simulateClick('#external'); + const internal = simulateClick('#internal'); + const custom = simulateClick('#custom'); + + expect(link.target).toBe('_blank'); + expect(link.rel).toBe('noopener noreferrer'); + + expect(internal.target).not.toBe('_blank'); + expect(custom.target).not.toBe('_blank'); + }); +}); diff --git a/app/javascript/shared/helpers/sessionStorage.js b/app/javascript/shared/helpers/sessionStorage.js new file mode 100644 index 000000000..db1eca2b8 --- /dev/null +++ b/app/javascript/shared/helpers/sessionStorage.js @@ -0,0 +1,26 @@ +export default { + clearAll() { + window.sessionStorage.clear(); + }, + + get(key) { + try { + const value = window.sessionStorage.getItem(key); + return value ? JSON.parse(value) : null; + } catch (error) { + return window.sessionStorage.getItem(key); + } + }, + + set(key, value) { + if (typeof value === 'object') { + window.sessionStorage.setItem(key, JSON.stringify(value)); + } else { + window.sessionStorage.setItem(key, value); + } + }, + + remove(key) { + window.sessionStorage.removeItem(key); + }, +}; diff --git a/app/javascript/shared/helpers/specs/sessionStorage.spec.js b/app/javascript/shared/helpers/specs/sessionStorage.spec.js new file mode 100644 index 000000000..899c6435a --- /dev/null +++ b/app/javascript/shared/helpers/specs/sessionStorage.spec.js @@ -0,0 +1,137 @@ +import SessionStorage from '../sessionStorage'; + +// Mocking sessionStorage +const sessionStorageMock = (() => { + let store = {}; + return { + getItem: key => store[key] || null, + setItem: (key, value) => { + store[key] = String(value); + }, + removeItem: key => delete store[key], + clear: () => { + store = {}; + }, + }; +})(); + +Object.defineProperty(window, 'sessionStorage', { + value: sessionStorageMock, +}); + +describe('SessionStorage utility', () => { + beforeEach(() => { + sessionStorage.clear(); + }); + + describe('clearAll method', () => { + it('should clear all items from sessionStorage', () => { + sessionStorage.setItem('testKey1', 'testValue1'); + sessionStorage.setItem('testKey2', 'testValue2'); + + SessionStorage.clearAll(); + + expect(sessionStorage.getItem('testKey1')).toBeNull(); + expect(sessionStorage.getItem('testKey2')).toBeNull(); + }); + }); + + describe('get method', () => { + it('should retrieve and parse JSON values correctly', () => { + const testObject = { a: 1, b: 'test' }; + sessionStorage.setItem('testKey', JSON.stringify(testObject)); + + expect(SessionStorage.get('testKey')).toEqual(testObject); + }); + + it('should return null for non-existent keys', () => { + expect(SessionStorage.get('nonExistentKey')).toBeNull(); + }); + + it('should handle non-JSON values by returning the raw value', () => { + sessionStorage.setItem('testKey', 'plain string value'); + + expect(SessionStorage.get('testKey')).toBe('plain string value'); + }); + + it('should handle malformed JSON gracefully', () => { + sessionStorage.setItem('testKey', '{malformed:json}'); + + expect(SessionStorage.get('testKey')).toBe('{malformed:json}'); + }); + }); + + describe('set method', () => { + it('should store object values as JSON strings', () => { + const testObject = { a: 1, b: 'test' }; + SessionStorage.set('testKey', testObject); + + expect(sessionStorage.getItem('testKey')).toBe( + JSON.stringify(testObject) + ); + }); + + it('should store primitive values directly', () => { + SessionStorage.set('stringKey', 'test string'); + expect(sessionStorage.getItem('stringKey')).toBe('test string'); + + SessionStorage.set('numberKey', 42); + expect(sessionStorage.getItem('numberKey')).toBe('42'); + + SessionStorage.set('booleanKey', true); + expect(sessionStorage.getItem('booleanKey')).toBe('true'); + }); + + it('should handle null values', () => { + SessionStorage.set('nullKey', null); + + expect(sessionStorage.getItem('nullKey')).toBe('null'); + expect(SessionStorage.get('nullKey')).toBeNull(); + }); + + it('should handle undefined values', () => { + SessionStorage.set('undefinedKey', undefined); + + expect(sessionStorage.getItem('undefinedKey')).toBe('undefined'); + }); + }); + + describe('remove method', () => { + it('should remove an item from sessionStorage', () => { + SessionStorage.set('testKey', 'testValue'); + expect(SessionStorage.get('testKey')).toBe('testValue'); + + SessionStorage.remove('testKey'); + + expect(SessionStorage.get('testKey')).toBeNull(); + }); + + it('should do nothing when removing a non-existent key', () => { + expect(() => { + SessionStorage.remove('nonExistentKey'); + }).not.toThrow(); + }); + }); + + describe('Integration of methods', () => { + it('should set, get, and remove values correctly', () => { + SessionStorage.set('testKey', { value: 'test' }); + + expect(SessionStorage.get('testKey')).toEqual({ value: 'test' }); + + SessionStorage.remove('testKey'); + expect(SessionStorage.get('testKey')).toBeNull(); + }); + + it('should correctly handle impersonation flag (common use case)', () => { + SessionStorage.set('impersonationUser', true); + + expect(SessionStorage.get('impersonationUser')).toBe(true); + + expect(sessionStorage.getItem('impersonationUser')).toBe('true'); + + SessionStorage.remove('impersonationUser'); + expect(SessionStorage.get('impersonationUser')).toBeNull(); + }); + }); +}); diff --git a/app/javascript/v3/views/login/Index.vue b/app/javascript/v3/views/login/Index.vue index 7a6fff3a8..a90368d08 100644 --- a/app/javascript/v3/views/login/Index.vue +++ b/app/javascript/v3/views/login/Index.vue @@ -6,7 +6,8 @@ import { parseBoolean } from '@chatwoot/utils'; import { useAlert } from 'dashboard/composables'; import { required, email } from '@vuelidate/validators'; import { useVuelidate } from '@vuelidate/core'; - +import { SESSION_STORAGE_KEYS } from 'dashboard/constants/sessionStorage'; +import SessionStorage from 'shared/helpers/sessionStorage'; // mixins import globalConfigMixin from 'shared/mixins/globalConfigMixin'; @@ -21,6 +22,8 @@ const ERROR_MESSAGES = { 'business-account-only': 'LOGIN.OAUTH.BUSINESS_ACCOUNTS_ONLY', }; +const IMPERSONATION_URL_SEARCH_KEY = 'impersonation'; + export default { components: { FormInput, @@ -112,6 +115,14 @@ export default { this.loginApi.message = message; useAlert(this.loginApi.message); }, + handleImpersonation() { + // Detects impersonation mode via URL and sets a session flag to prevent user settings changes during impersonation. + const urlParams = new URLSearchParams(window.location.search); + const impersonation = urlParams.get(IMPERSONATION_URL_SEARCH_KEY); + if (impersonation) { + SessionStorage.set(SESSION_STORAGE_KEYS.IMPERSONATION_USER, true); + } + }, submitLogin() { this.loginApi.hasErrored = false; this.loginApi.showLoading = true; @@ -128,6 +139,7 @@ export default { login(credentials) .then(() => { + this.handleImpersonation(); this.showAlertMessage(this.$t('LOGIN.API.SUCCESS_MESSAGE')); }) .catch(response => { diff --git a/app/jobs/internal/delete_accounts_job.rb b/app/jobs/internal/delete_accounts_job.rb new file mode 100644 index 000000000..311915ed7 --- /dev/null +++ b/app/jobs/internal/delete_accounts_job.rb @@ -0,0 +1,27 @@ +class Internal::DeleteAccountsJob < ApplicationJob + queue_as :scheduled_jobs + + def perform + delete_expired_accounts + end + + private + + def delete_expired_accounts + accounts_pending_deletion.each do |account| + AccountDeletionService.new(account: account).perform + end + end + + def accounts_pending_deletion + Account.where("custom_attributes->>'marked_for_deletion_at' IS NOT NULL") + .select { |account| deletion_period_expired?(account) } + end + + def deletion_period_expired?(account) + deletion_time = account.custom_attributes['marked_for_deletion_at'] + return false if deletion_time.blank? + + DateTime.parse(deletion_time) <= Time.current + end +end diff --git a/app/listeners/action_cable_listener.rb b/app/listeners/action_cable_listener.rb index 37dc939e7..109f6d344 100644 --- a/app/listeners/action_cable_listener.rb +++ b/app/listeners/action_cable_listener.rb @@ -204,3 +204,5 @@ class ActionCableListener < BaseListener ::ActionCableBroadcastJob.perform_later(tokens.uniq, event_name, payload) end end + +ActionCableListener.prepend_mod_with('ActionCableListener') diff --git a/app/mailboxes/application_mailbox.rb b/app/mailboxes/application_mailbox.rb index 4fe931b9f..9fc7a435d 100644 --- a/app/mailboxes/application_mailbox.rb +++ b/app/mailboxes/application_mailbox.rb @@ -31,7 +31,7 @@ class ApplicationMailbox < ActionMailbox::Base end def in_reply_to_matches?(in_reply_to) - Array.wrap(in_reply_to).any? { _1.match?(CONVERSATION_MESSAGE_ID_PATTERN) } + Array.wrap(in_reply_to).any? { it.match?(CONVERSATION_MESSAGE_ID_PATTERN) } end # checks if follow this pattern send it to reply_mailbox diff --git a/app/mailers/administrator_notifications/account_compliance_mailer.rb b/app/mailers/administrator_notifications/account_compliance_mailer.rb new file mode 100644 index 000000000..61ded60d3 --- /dev/null +++ b/app/mailers/administrator_notifications/account_compliance_mailer.rb @@ -0,0 +1,52 @@ +class AdministratorNotifications::AccountComplianceMailer < AdministratorNotifications::BaseMailer + def account_deleted(account) + return if instance_admin_email.blank? + + subject = subject_for(account) + meta = build_meta(account) + + send_notification(subject, to: instance_admin_email, meta: meta) + end + + private + + def build_meta(account) + deleted_users = params[:soft_deleted_users] || [] + + user_info_list = deleted_users.map do |user| + { + 'user_id' => user[:id].to_s, + 'user_email' => user[:original_email].to_s + } + end + + { + 'instance_url' => instance_url, + 'account_id' => account.id, + 'account_name' => account.name, + 'deleted_at' => format_time(Time.current.iso8601), + 'deletion_reason' => account.custom_attributes['marked_for_deletion_reason'] || 'not specified', + 'marked_for_deletion_at' => format_time(account.custom_attributes['marked_for_deletion_at']), + 'soft_deleted_users' => user_info_list, + 'deleted_user_count' => user_info_list.size + } + end + + def format_time(time_string) + return 'not specified' if time_string.blank? + + Time.zone.parse(time_string).strftime('%B %d, %Y %H:%M:%S %Z') + end + + def subject_for(account) + "Account Deletion Notice for #{account.id} - #{account.name}" + end + + def instance_admin_email + GlobalConfig.get('CHATWOOT_INSTANCE_ADMIN_EMAIL')['CHATWOOT_INSTANCE_ADMIN_EMAIL'] + end + + def instance_url + ENV.fetch('FRONTEND_URL', 'not available') + end +end diff --git a/app/models/account.rb b/app/models/account.rb index 1664ce29b..fb156511b 100644 --- a/app/models/account.rb +++ b/app/models/account.rb @@ -97,8 +97,8 @@ class Account < ApplicationRecord has_one_attached :contacts_export - enum locale: LANGUAGES_CONFIG.map { |key, val| [val[:iso_639_1_code], key] }.to_h - enum status: { active: 0, suspended: 1 } + enum :locale, LANGUAGES_CONFIG.map { |key, val| [val[:iso_639_1_code], key] }.to_h, prefix: true + enum :status, { active: 0, suspended: 1 } scope :with_auto_resolve, -> { where("(settings ->> 'auto_resolve_after')::int IS NOT NULL") } diff --git a/app/models/concerns/activity_message_handler.rb b/app/models/concerns/activity_message_handler.rb index 54e58b4d9..0d6741c7a 100644 --- a/app/models/concerns/activity_message_handler.rb +++ b/app/models/concerns/activity_message_handler.rb @@ -5,6 +5,7 @@ module ActivityMessageHandler include LabelActivityMessageHandler include SlaActivityMessageHandler include TeamActivityMessageHandler + include CsatActivityMessageHandler private diff --git a/app/models/concerns/csat_activity_message_handler.rb b/app/models/concerns/csat_activity_message_handler.rb new file mode 100644 index 000000000..7a2488c4d --- /dev/null +++ b/app/models/concerns/csat_activity_message_handler.rb @@ -0,0 +1,8 @@ +module CsatActivityMessageHandler + extend ActiveSupport::Concern + + def create_csat_not_sent_activity_message + content = I18n.t('conversations.activity.csat.not_sent_due_to_messaging_window') + ::Conversations::ActivityMessageJob.perform_later(self, activity_message_params(content)) if content + end +end diff --git a/app/models/concerns/sso_authenticatable.rb b/app/models/concerns/sso_authenticatable.rb index e228ca2c2..98b50f336 100644 --- a/app/models/concerns/sso_authenticatable.rb +++ b/app/models/concerns/sso_authenticatable.rb @@ -20,6 +20,10 @@ module SsoAuthenticatable "#{ENV.fetch('FRONTEND_URL', nil)}/app/login?email=#{encoded_email}&sso_auth_token=#{generate_sso_auth_token}" end + def generate_sso_link_with_impersonation + "#{generate_sso_link}&impersonation=true" + end + private def sso_token_key(token) diff --git a/app/models/conversation.rb b/app/models/conversation.rb index bcb79c05b..3f1a20037 100644 --- a/app/models/conversation.rb +++ b/app/models/conversation.rb @@ -128,6 +128,12 @@ class Conversation < ApplicationRecord additional_attributes&.dig('conversation_language') end + # Be aware: The precision of created_at and last_activity_at may differ from Ruby's Time precision. + # Our DB column (see schema) stores timestamps with second-level precision (no microseconds), so + # if you assign a Ruby Time with microseconds, the DB will truncate it. This may cause subtle differences + # if you compare or copy these values in Ruby, also in our specs + # So in specs rely on to be_with(1.second) instead of to eq() + # TODO: Migrate to use a timestamp with microsecond precision def last_activity_at self[:last_activity_at] || created_at end diff --git a/app/models/email_template.rb b/app/models/email_template.rb index 5891626a4..57db7a94c 100644 --- a/app/models/email_template.rb +++ b/app/models/email_template.rb @@ -16,8 +16,8 @@ # index_email_templates_on_name_and_account_id (name,account_id) UNIQUE # class EmailTemplate < ApplicationRecord - enum locale: LANGUAGES_CONFIG.map { |key, val| [val[:iso_639_1_code], key] }.to_h - enum template_type: { layout: 0, content: 1 } + enum :locale, LANGUAGES_CONFIG.map { |key, val| [val[:iso_639_1_code], key] }.to_h, prefix: true + enum :template_type, { layout: 0, content: 1 } belongs_to :account, optional: true validates :name, uniqueness: { scope: :account } diff --git a/app/models/installation_config.rb b/app/models/installation_config.rb index f1603e4ca..5ea438916 100644 --- a/app/models/installation_config.rb +++ b/app/models/installation_config.rb @@ -19,7 +19,7 @@ class InstallationConfig < ApplicationRecord # https://discuss.rubyonrails.org/t/cve-2022-32224-possible-rce-escalation-bug-with-serialized-columns-in-active-record/81017 # FIX ME : fixes breakage of installation config. we need to migrate. # Fix configuration in application.rb - serialize :serialized_value, ActiveSupport::HashWithIndifferentAccess + serialize :serialized_value, coder: YAML, type: ActiveSupport::HashWithIndifferentAccess before_validation :set_lock validates :name, presence: true @@ -32,6 +32,10 @@ class InstallationConfig < ApplicationRecord after_commit :clear_cache def value + # This is an extra hack again cause of the YAML serialization, in case of new object initialization in super admin + # It was throwing error as the default value of column '{}' was failing in deserialization. + return {}.with_indifferent_access if new_record? && @attributes['serialized_value']&.value_before_type_cast == '{}' + serialized_value[:value] end diff --git a/app/models/integrations/app.rb b/app/models/integrations/app.rb index d4e563f81..dfe889bfa 100644 --- a/app/models/integrations/app.rb +++ b/app/models/integrations/app.rb @@ -39,7 +39,8 @@ class Integrations::App def action case params[:id] when 'slack' - "#{params[:action]}&client_id=#{ENV.fetch('SLACK_CLIENT_ID', nil)}&redirect_uri=#{self.class.slack_integration_url}" + client_id = GlobalConfigService.load('SLACK_CLIENT_ID', nil) + "#{params[:action]}&client_id=#{client_id}&redirect_uri=#{self.class.slack_integration_url}" when 'linear' build_linear_action else @@ -50,7 +51,7 @@ class Integrations::App def active?(account) case params[:id] when 'slack' - ENV['SLACK_CLIENT_SECRET'].present? + GlobalConfigService.load('SLACK_CLIENT_SECRET', nil).present? when 'linear' GlobalConfigService.load('LINEAR_CLIENT_ID', nil).present? when 'shopify' diff --git a/app/models/message.rb b/app/models/message.rb index f5d7712d2..a952e0265 100644 --- a/app/models/message.rb +++ b/app/models/message.rb @@ -185,7 +185,13 @@ class Message < ApplicationRecord # move this to a presenter return self[:content] if !input_csat? || inbox.web_widget? - I18n.t('conversations.survey.response', link: "#{ENV.fetch('FRONTEND_URL', nil)}/survey/responses/#{conversation.uuid}") + survey_link = "#{ENV.fetch('FRONTEND_URL', nil)}/survey/responses/#{conversation.uuid}" + + if inbox.csat_config&.dig('message').present? + "#{inbox.csat_config['message']} #{survey_link}" + else + I18n.t('conversations.survey.response', link: survey_link) + end end def email_notifiable_message? diff --git a/app/models/user.rb b/app/models/user.rb index 5594b0ca6..d1907362a 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -109,8 +109,8 @@ class User < ApplicationRecord self.email = email.try(:downcase) end - def send_devise_notification(notification, *args) - devise_mailer.with(account: Current.account).send(notification, self, *args).deliver_later + def send_devise_notification(notification, *) + devise_mailer.with(account: Current.account).send(notification, self, *).deliver_later end def set_password_and_uid diff --git a/app/services/account/sign_up_email_validation_service.rb b/app/services/account/sign_up_email_validation_service.rb new file mode 100644 index 000000000..8889145b2 --- /dev/null +++ b/app/services/account/sign_up_email_validation_service.rb @@ -0,0 +1,36 @@ +# frozen_string_literal: true + +class Account::SignUpEmailValidationService + include CustomExceptions::Account + attr_reader :email + + def initialize(email) + @email = email + end + + def perform + address = ValidEmail2::Address.new(email) + + raise InvalidEmail.new({ valid: false, disposable: nil }) unless address.valid? + + raise InvalidEmail.new({ domain_blocked: true }) if domain_blocked? + + raise InvalidEmail.new({ valid: true, disposable: true }) if address.disposable? + + true + end + + private + + def domain_blocked? + domain = email.split('@').last&.downcase + blocked_domains.any? { |blocked_domain| domain.match?(blocked_domain.downcase) } + end + + def blocked_domains + domains = GlobalConfigService.load('BLOCKED_EMAIL_DOMAINS', '') + return [] if domains.blank? + + domains.split("\n").map(&:strip) + end +end diff --git a/app/services/account_deletion_service.rb b/app/services/account_deletion_service.rb new file mode 100644 index 000000000..62bce601b --- /dev/null +++ b/app/services/account_deletion_service.rb @@ -0,0 +1,50 @@ +class AccountDeletionService + attr_reader :account, :soft_deleted_users + + def initialize(account:) + @account = account + @soft_deleted_users = [] + end + + def perform + Rails.logger.info("Deleting account #{account.id} - #{account.name} that was marked for deletion") + + soft_delete_orphaned_users + send_compliance_notification + DeleteObjectJob.perform_later(account) + end + + private + + def send_compliance_notification + AdministratorNotifications::AccountComplianceMailer.with( + account: account, + soft_deleted_users: soft_deleted_users + ).account_deleted(account).deliver_later + end + + def soft_delete_orphaned_users + account.users.each do |user| + # Find all account_users for this user excluding the current account + other_accounts = user.account_users.where.not(account_id: account.id).count + + # If user has no other accounts, soft delete them + next unless other_accounts.zero? + + # Soft delete user by appending -deleted.com to email + original_email = user.email + user.email = "#{original_email}-deleted.com" + user.skip_reconfirmation! + user.save! + + user_info = { + id: user.id.to_s, + original_email: original_email + } + + soft_deleted_users << user_info + + Rails.logger.info("Soft deleted user #{user.id} with email #{original_email}") + end + end +end diff --git a/app/services/conversations/filter_service.rb b/app/services/conversations/filter_service.rb index 21fe3c716..db2892c31 100644 --- a/app/services/conversations/filter_service.rb +++ b/app/services/conversations/filter_service.rb @@ -28,16 +28,6 @@ class Conversations::FilterService < FilterService :taggings, :inbox, { assignee: { avatar_attachment: [:blob] } }, { contact: { avatar_attachment: [:blob] } }, :team, :messages, :contact_inbox ) - account_user = @account.account_users.find_by(user_id: @user.id) - is_administrator = account_user&.role == 'administrator' - - # Ensure we only include conversations from inboxes the user has access to - unless is_administrator - inbox_ids = @user.inboxes.where(account_id: @account.id).pluck(:id) - conversations = conversations.where(inbox_id: inbox_ids) - end - - # Apply permission-based filtering Conversations::PermissionFilterService.new( conversations, @user, diff --git a/app/services/conversations/permission_filter_service.rb b/app/services/conversations/permission_filter_service.rb index a8561dbdf..31dd011d7 100644 --- a/app/services/conversations/permission_filter_service.rb +++ b/app/services/conversations/permission_filter_service.rb @@ -8,9 +8,23 @@ class Conversations::PermissionFilterService end def perform - # The base implementation simply returns all conversations - # Enterprise edition extends this with permission-based filtering - conversations + return conversations if user_role == 'administrator' + + accessible_conversations + end + + private + + def accessible_conversations + conversations.where(inbox: user.inboxes.where(account_id: account.id)) + end + + def account_user + AccountUser.find_by(account_id: account.id, user_id: user.id) + end + + def user_role + account_user&.role end end diff --git a/app/services/crm/leadsquared/lead_finder_service.rb b/app/services/crm/leadsquared/lead_finder_service.rb index cf3014099..59318b818 100644 --- a/app/services/crm/leadsquared/lead_finder_service.rb +++ b/app/services/crm/leadsquared/lead_finder_service.rb @@ -31,7 +31,11 @@ class Crm::Leadsquared::LeadFinderService def find_by_phone_number(contact) return if contact.phone_number.blank? - search_by_field(contact.phone_number) + lead_data = Crm::Leadsquared::Mappers::ContactMapper.map(contact) + + return if lead_data.blank? || lead_data['Mobile'].nil? + + search_by_field(lead_data['Mobile']) end def search_by_field(value) diff --git a/app/services/crm/leadsquared/mappers/contact_mapper.rb b/app/services/crm/leadsquared/mappers/contact_mapper.rb index 0196116bb..dc51a1498 100644 --- a/app/services/crm/leadsquared/mappers/contact_mapper.rb +++ b/app/services/crm/leadsquared/mappers/contact_mapper.rb @@ -20,11 +20,29 @@ class Crm::Leadsquared::Mappers::ContactMapper 'FirstName' => contact.name.presence, 'LastName' => contact.last_name.presence, 'EmailAddress' => contact.email.presence, - 'Mobile' => contact.phone_number.presence, + 'Mobile' => formatted_phone_number, 'Source' => brand_name }.compact end + def formatted_phone_number + # it seems like leadsquared needs a different phone number format + # it's not documented anywhere, so don't bother trying to look up online + # After some trial and error, I figured out the format, its +- + return nil if contact.phone_number.blank? + + parsed = TelephoneNumber.parse(contact.phone_number) + return contact.phone_number unless parsed.valid? + + country_code = parsed.country.country_code + e164 = parsed.e164_number + e164 = e164.sub(/^\+/, '') + + national_number = e164.sub(/^#{Regexp.escape(country_code)}/, '') + + "+#{country_code}-#{national_number}" + end + def brand_name ::GlobalConfig.get('BRAND_NAME')['BRAND_NAME'] || 'Chatwoot' end diff --git a/app/services/crm/leadsquared/mappers/conversation_mapper.rb b/app/services/crm/leadsquared/mappers/conversation_mapper.rb index 97a148435..c5c358cbf 100644 --- a/app/services/crm/leadsquared/mappers/conversation_mapper.rb +++ b/app/services/crm/leadsquared/mappers/conversation_mapper.rb @@ -6,17 +6,18 @@ class Crm::Leadsquared::Mappers::ConversationMapper # so this limits it ACTIVITY_NOTE_MAX_SIZE = 1800 - def self.map_conversation_activity(conversation) - new(conversation).conversation_activity + def self.map_conversation_activity(hook, conversation) + new(hook, conversation).conversation_activity end - def self.map_transcript_activity(conversation, messages = nil) - new(conversation, messages).transcript_activity + def self.map_transcript_activity(hook, conversation) + new(hook, conversation).transcript_activity end - def initialize(conversation, messages = nil) + def initialize(hook, conversation) + @hook = hook + @timezone = Time.find_zone(hook.settings['timezone']) || Time.zone @conversation = conversation - @messages = messages end def conversation_activity @@ -41,14 +42,14 @@ class Crm::Leadsquared::Mappers::ConversationMapper private - attr_reader :conversation, :messages + attr_reader :conversation def formatted_creation_time - conversation.created_at.strftime('%Y-%m-%d %H:%M:%S') + conversation.created_at.in_time_zone(@timezone).strftime('%Y-%m-%d %H:%M:%S') end def transcript_messages - @transcript_messages ||= messages || conversation.messages.chat.select(&:conversation_transcriptable?) + @transcript_messages ||= conversation.messages.chat.select(&:conversation_transcriptable?) end def format_messages @@ -77,8 +78,7 @@ class Crm::Leadsquared::Mappers::ConversationMapper end def message_time(message) - # TODO: Figure out what timezone to send the time in - message.created_at.strftime('%Y-%m-%d %H:%M') + message.created_at.in_time_zone(@timezone).strftime('%Y-%m-%d %H:%M') end def sender_name(message) diff --git a/app/services/crm/leadsquared/processor_service.rb b/app/services/crm/leadsquared/processor_service.rb index aedea51d7..ef33718f2 100644 --- a/app/services/crm/leadsquared/processor_service.rb +++ b/app/services/crm/leadsquared/processor_service.rb @@ -37,7 +37,7 @@ class Crm::Leadsquared::ProcessorService < Crm::BaseProcessorService activity_type: 'conversation', activity_code_key: 'conversation_activity_code', metadata_key: 'created_activity_id', - activity_note: Crm::Leadsquared::Mappers::ConversationMapper.map_conversation_activity(conversation) + activity_note: Crm::Leadsquared::Mappers::ConversationMapper.map_conversation_activity(@hook, conversation) ) end @@ -50,7 +50,7 @@ class Crm::Leadsquared::ProcessorService < Crm::BaseProcessorService activity_type: 'transcript', activity_code_key: 'transcript_activity_code', metadata_key: 'transcript_activity_id', - activity_note: Crm::Leadsquared::Mappers::ConversationMapper.map_transcript_activity(conversation) + activity_note: Crm::Leadsquared::Mappers::ConversationMapper.map_transcript_activity(@hook, conversation) ) end diff --git a/app/services/crm/leadsquared/setup_service.rb b/app/services/crm/leadsquared/setup_service.rb index 956ff1a10..0433f68fd 100644 --- a/app/services/crm/leadsquared/setup_service.rb +++ b/app/services/crm/leadsquared/setup_service.rb @@ -25,11 +25,12 @@ class Crm::Leadsquared::SetupService response = @client.get('Authentication.svc/UserByAccessKey.Get') endpoint_host = response['LSQCommonServiceURLs']['api'] app_host = response['LSQCommonServiceURLs']['app'] + timezone = response['TimeZone'] endpoint_url = "https://#{endpoint_host}/v2/" app_url = "https://#{app_host}/" - update_hook_settings({ :endpoint_url => endpoint_url, :app_url => app_url }) + update_hook_settings({ :endpoint_url => endpoint_url, :app_url => app_url, :timezone => timezone }) # replace the clients @client = Crm::Leadsquared::Api::BaseClient.new(@access_key, @secret_key, endpoint_url) diff --git a/app/services/message_templates/hook_execution_service.rb b/app/services/message_templates/hook_execution_service.rb index bc161d885..8291c8a9c 100644 --- a/app/services/message_templates/hook_execution_service.rb +++ b/app/services/message_templates/hook_execution_service.rb @@ -17,7 +17,7 @@ class MessageTemplates::HookExecutionService ::MessageTemplates::Template::OutOfOffice.new(conversation: conversation).perform if should_send_out_of_office_message? ::MessageTemplates::Template::Greeting.new(conversation: conversation).perform if should_send_greeting? ::MessageTemplates::Template::EmailCollect.new(conversation: conversation).perform if inbox.enable_email_collect && should_send_email_collect? - ::MessageTemplates::Template::CsatSurvey.new(conversation: conversation).perform if should_send_csat_survey? + handle_csat_survey end def should_send_out_of_office_message? @@ -27,7 +27,7 @@ class MessageTemplates::HookExecutionService return false unless message.incoming? # prevents sending out-of-office message if an agent has sent a message in last 5 minutes # ensures better UX by not interrupting active conversations at the end of business hours - return false if conversation.messages.outgoing.exists?(['created_at > ?', 5.minutes.ago]) + return false if conversation.messages.outgoing.where(private: false).exists?(['created_at > ?', 5.minutes.ago]) inbox.out_of_office? && conversation.messages.today.template.empty? && inbox.out_of_office_message.present? end @@ -65,13 +65,26 @@ class MessageTemplates::HookExecutionService true end - def should_send_csat_survey? + def handle_csat_survey return unless csat_enabled_conversation? - # only send CSAT once in a conversation - return if conversation.messages.where(content_type: :input_csat).present? + return if csat_already_sent? - true + # Only send CSAT if agent can still reply by checking the messaging window restriction + # https://www.chatwoot.com/docs/self-hosted/supported-features#outgoing-message-restriction + if within_messaging_window? + ::MessageTemplates::Template::CsatSurvey.new(conversation: conversation).perform + else + conversation.create_csat_not_sent_activity_message + end + end + + def csat_already_sent? + conversation.messages.where(content_type: :input_csat).present? + end + + def within_messaging_window? + conversation.can_reply? end end MessageTemplates::HookExecutionService.prepend_mod_with('MessageTemplates::HookExecutionService') diff --git a/app/services/message_templates/template/csat_survey.rb b/app/services/message_templates/template/csat_survey.rb index 3a7ca2605..dd9cf3bd6 100644 --- a/app/services/message_templates/template/csat_survey.rb +++ b/app/services/message_templates/template/csat_survey.rb @@ -12,7 +12,6 @@ class MessageTemplates::Template::CsatSurvey private delegate :contact, :account, :inbox, to: :conversation - delegate :csat_config, to: :inbox def should_send_csat_survey? return true unless survey_rules_configured? diff --git a/app/services/search_service.rb b/app/services/search_service.rb index 5999c88a6..40d862b19 100644 --- a/app/services/search_service.rb +++ b/app/services/search_service.rb @@ -9,8 +9,10 @@ class SearchService { conversations: filter_conversations } when 'Contact' { contacts: filter_contacts } + when 'Article' + { articles: filter_articles } else - { contacts: filter_contacts, messages: filter_messages, conversations: filter_conversations } + { contacts: filter_contacts, messages: filter_messages, conversations: filter_conversations, articles: filter_articles } end end @@ -90,4 +92,12 @@ class SearchService ILIKE :search OR identifier ILIKE :search", search: "%#{search_query}%" ).resolved_contacts.order_on_last_activity_at('desc').page(params[:page]).per(15) end + + def filter_articles + @articles = current_account.articles + .text_search(search_query) + .reorder('updated_at DESC') + .page(params[:page]) + .per(15) + end end diff --git a/app/services/twilio/incoming_message_service.rb b/app/services/twilio/incoming_message_service.rb index 7a335c87d..c38577599 100644 --- a/app/services/twilio/incoming_message_service.rb +++ b/app/services/twilio/incoming_message_service.rb @@ -137,14 +137,19 @@ class Twilio::IncomingMessageService end def download_with_auth(media_url) - Down.download( - media_url, - http_basic_authentication: [twilio_channel.account_sid, twilio_channel.auth_token || twilio_channel.api_key_sid] - ) + auth_credentials = if twilio_channel.api_key_sid.present? + # When using api_key_sid, the auth token should be the api_secret_key + [twilio_channel.api_key_sid, twilio_channel.auth_token] + else + # When using account_sid, the auth token is the account's auth token + [twilio_channel.account_sid, twilio_channel.auth_token] + end + + Down.download(media_url, http_basic_authentication: auth_credentials) end def handle_download_attachment_error(error, media_url) - Rails.logger.info "Error downloading attachment from Twilio: #{error.message}: Retrying" + Rails.logger.info "Error downloading attachment from Twilio: #{error.message}: Retrying without auth" Down.download(media_url) rescue StandardError => e Rails.logger.info "Error downloading attachment from Twilio: #{e.message}: Skipping" diff --git a/app/views/api/v1/accounts/search/_article.json.jbuilder b/app/views/api/v1/accounts/search/_article.json.jbuilder new file mode 100644 index 000000000..a3cf94614 --- /dev/null +++ b/app/views/api/v1/accounts/search/_article.json.jbuilder @@ -0,0 +1,8 @@ +json.id article.id +json.title article.title +json.locale article.locale +json.content article.content +json.slug article.slug +json.portal_slug article.portal.slug +json.account_id article.account_id +json.category_name article.category&.name diff --git a/app/views/api/v1/accounts/search/_conversation_search_result.json.jbuilder b/app/views/api/v1/accounts/search/_conversation_search_result.json.jbuilder new file mode 100644 index 000000000..a0b7e0203 --- /dev/null +++ b/app/views/api/v1/accounts/search/_conversation_search_result.json.jbuilder @@ -0,0 +1,15 @@ +json.id conversation.display_id +json.account_id conversation.account_id +json.created_at conversation.created_at.to_i +json.message do + json.partial! 'message', formats: [:json], message: conversation.messages.try(:first) +end +json.contact do + json.partial! 'contact', formats: [:json], contact: conversation.contact if conversation.try(:contact).present? +end +json.inbox do + json.partial! 'inbox', formats: [:json], inbox: conversation.inbox if conversation.try(:inbox).present? +end +json.agent do + json.partial! 'agent', formats: [:json], agent: conversation.assignee if conversation.try(:assignee).present? +end diff --git a/app/views/api/v1/accounts/search/articles.json.jbuilder b/app/views/api/v1/accounts/search/articles.json.jbuilder new file mode 100644 index 000000000..7d4fe031c --- /dev/null +++ b/app/views/api/v1/accounts/search/articles.json.jbuilder @@ -0,0 +1,7 @@ +json.payload do + json.articles do + json.array! @result[:articles] do |article| + json.partial! 'article', formats: [:json], article: article + end + end +end \ No newline at end of file diff --git a/app/views/api/v1/accounts/search/index.json.jbuilder b/app/views/api/v1/accounts/search/index.json.jbuilder index 1c6e86284..a3d8f1858 100644 --- a/app/views/api/v1/accounts/search/index.json.jbuilder +++ b/app/views/api/v1/accounts/search/index.json.jbuilder @@ -1,21 +1,7 @@ json.payload do json.conversations do json.array! @result[:conversations] do |conversation| - json.id conversation.display_id - json.account_id conversation.account_id - json.created_at conversation.created_at.to_i - json.message do - json.partial! 'message', formats: [:json], message: conversation.messages.try(:first) - end - json.contact do - json.partial! 'contact', formats: [:json], contact: conversation.contact if conversation.try(:contact).present? - end - json.inbox do - json.partial! 'inbox', formats: [:json], inbox: conversation.inbox if conversation.try(:inbox).present? - end - json.agent do - json.partial! 'agent', formats: [:json], agent: conversation.assignee if conversation.try(:assignee).present? - end + json.partial! 'conversation_search_result', formats: [:json], conversation: conversation end end json.contacts do @@ -23,10 +9,14 @@ json.payload do json.partial! 'contact', formats: [:json], contact: contact end end - json.messages do json.array! @result[:messages] do |message| json.partial! 'message', formats: [:json], message: message end end + json.articles do + json.array! @result[:articles] do |article| + json.partial! 'article', formats: [:json], article: article + end + end end diff --git a/app/views/mailers/administrator_notifications/account_compliance_mailer/account_deleted.liquid b/app/views/mailers/administrator_notifications/account_compliance_mailer/account_deleted.liquid new file mode 100644 index 000000000..636a5daa2 --- /dev/null +++ b/app/views/mailers/administrator_notifications/account_compliance_mailer/account_deleted.liquid @@ -0,0 +1,30 @@ +Hello, + +This is a notification to inform you that an account has been permanently deleted from your Chatwoot instance. + + + Chatwoot Installation: {{ meta.instance_url }} + Account ID: {{ meta.account_id }} + Account Name: {{ meta.account_name }} + Deleted At: {{ meta.deleted_at }} + Marked for Deletion at: {{ meta.marked_for_deletion_at }} + Deletion Reason: {{ meta.deletion_reason }} + + +{% if meta.deleted_user_count > 0 %} + + Deleted Users ({{ meta.deleted_user_count }}): + {% for user in meta.soft_deleted_users %} + User ID: {{ user.user_id }}, Email: {{ user.user_email }}{% unless forloop.last %}{% endunless %} + {% endfor %} + +{% else %} + + Deleted Users: None + +{% endif %} + +This email serves as a record for compliance purposes. + +Thank you, +Chatwoot System \ No newline at end of file diff --git a/app/views/platform/api/v1/users/token.json.jbuilder b/app/views/platform/api/v1/users/token.json.jbuilder new file mode 100644 index 000000000..d9d5468d7 --- /dev/null +++ b/app/views/platform/api/v1/users/token.json.jbuilder @@ -0,0 +1,9 @@ +json.access_token @resource.access_token.token +json.expiry nil +json.user do + json.id @resource.id + json.name @resource.name + json.display_name @resource.display_name + json.email @resource.email + json.pubsub_token @resource.pubsub_token +end diff --git a/app/views/super_admin/application/_icons.html.erb b/app/views/super_admin/application/_icons.html.erb index fb10c1035..fabff914b 100644 --- a/app/views/super_admin/application/_icons.html.erb +++ b/app/views/super_admin/application/_icons.html.erb @@ -159,4 +159,7 @@ - \ No newline at end of file + + + + diff --git a/app/views/super_admin/users/_impersonate.erb b/app/views/super_admin/users/_impersonate.erb index dff1cda57..4d758b88d 100644 --- a/app/views/super_admin/users/_impersonate.erb +++ b/app/views/super_admin/users/_impersonate.erb @@ -2,7 +2,7 @@ Caution: Any actions executed after impersonate will appear as if performed by the impersonated user - [<%= page.resource.name %> ] - Impersonate user + Impersonate user diff --git a/config/app.yml b/config/app.yml index 9afc3f7bc..36bbdbcf1 100644 --- a/config/app.yml +++ b/config/app.yml @@ -1,5 +1,5 @@ shared: &shared - version: '4.1.0' + version: '4.2.0' development: <<: *shared diff --git a/config/initializers/01_redis.rb b/config/initializers/01_redis.rb index 664dcd435..93c12fce7 100644 --- a/config/initializers/01_redis.rb +++ b/config/initializers/01_redis.rb @@ -1,3 +1,7 @@ +# TODO: Phase out the custom ConnectionPool wrappers ($alfred / $velma), +# switch to plain Redis clients here and let Rails 7.1+ handle pooling +# via `pool:` in RedisCacheStore (see rack_attack initializer). + # Alfred # Add here as you use it for more features # Used for Round Robin, Conversation Emails & Online Presence diff --git a/config/initializers/cors.rb b/config/initializers/cors.rb index 1d516370f..38878d456 100644 --- a/config/initializers/cors.rb +++ b/config/initializers/cors.rb @@ -14,6 +14,10 @@ Rails.application.config.middleware.insert_before 0, Rack::Cors do if ActiveModel::Type::Boolean.new.cast(ENV.fetch('CW_API_ONLY_SERVER', false)) || Rails.env.development? resource '*', headers: :any, methods: :any, expose: %w[access-token client uid expiry] end + + if ActiveModel::Type::Boolean.new.cast(ENV.fetch('ENABLE_API_CORS', false)) + resource '/api/*', headers: :any, methods: :any, expose: %w[access-token client uid expiry] + end end end diff --git a/config/initializers/rack_attack.rb b/config/initializers/rack_attack.rb index fdbd47008..fe3f6c554 100644 --- a/config/initializers/rack_attack.rb +++ b/config/initializers/rack_attack.rb @@ -11,7 +11,12 @@ class Rack::Attack # Rack::Attack.cache.store = ActiveSupport::Cache::MemoryStore.new # https://github.com/rack/rack-attack/issues/102 - Rack::Attack.cache.store = ActiveSupport::Cache::RedisCacheStore.new(redis: $velma) + # Rails 7.1 automatically adds its own ConnectionPool around RedisCacheStore. + # Because `$velma` is *already* a ConnectionPool, double-wrapping causes + # Redis calls like `get` to hit the outer wrapper and explode. + # `pool: false` tells Rails to skip its internal pool and use ours directly. + # TODO: We can use build in connection pool in future upgrade + Rack::Attack.cache.store = ActiveSupport::Cache::RedisCacheStore.new(redis: $velma, pool: false) class Request < ::Rack::Request # You many need to specify a method to fetch the correct remote IP address diff --git a/config/installation_config.yml b/config/installation_config.yml index e9f049496..6c9019474 100644 --- a/config/installation_config.yml +++ b/config/installation_config.yml @@ -255,6 +255,14 @@ description: Used to notify Chatwoot about account abuses, potential threads (Should be a Discord Webhook URL) # ------- End of Chatwoot Internal Config for Self Hosted ----# +# ------- Compliance Related Config ----# +- name: CHATWOOT_INSTANCE_ADMIN_EMAIL + display_title: 'Instance Admin Email' + value: + description: 'The email of the instance administrator to receive compliance-related notifications' + locked: false +# ------- End of Compliance Related Config ----# + ## ------ Configs added for enterprise clients ------ ## - name: API_CHANNEL_NAME value: @@ -300,6 +308,20 @@ type: secret ## ------ End of Configs added for Linear ------ ## +## ------ Configs added for Slack ------ ## +- name: SLACK_CLIENT_ID + display_title: 'Slack Client ID' + value: + locked: false + description: 'Slack client ID' +- name: SLACK_CLIENT_SECRET + display_title: 'Slack Client Secret' + value: + locked: false + description: 'Slack client secret' + type: secret +## ------ End of Configs added for Slack ------ ## + # ------- Shopify Integration Config ------- # - name: SHOPIFY_CLIENT_ID display_title: 'Shopify Client ID' diff --git a/config/integration/apps.yml b/config/integration/apps.yml index 10ba2e056..2921bf637 100644 --- a/config/integration/apps.yml +++ b/config/integration/apps.yml @@ -205,6 +205,7 @@ leadsquared: 'secret_key': { 'type': 'string' }, 'endpoint_url': { 'type': 'string' }, 'app_url': { 'type': 'string' }, + 'timezone': { 'type': 'string' }, 'enable_conversation_activity': { 'type': 'boolean' }, 'enable_transcript_activity': { 'type': 'boolean' }, 'conversation_activity_score': { 'type': 'string' }, diff --git a/config/locales/en.yml b/config/locales/en.yml index 2a2e73d91..b309e718c 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -185,6 +185,8 @@ en: removed: '%{user_name} removed %{labels}' sla: added: '%{user_name} added SLA policy %{sla_name}' + csat: + not_sent_due_to_messaging_window: 'CSAT survey not sent due to outgoing message restrictions' removed: '%{user_name} removed SLA policy %{sla_name}' muted: '%{user_name} has muted the conversation' unmuted: '%{user_name} has unmuted the conversation' @@ -213,33 +215,41 @@ en: online: delete: '%{contact_name} is Online, please try again later' integration_apps: + # Note: webhooks and dashboard_apps don't need short_description as they use different modal components dashboard_apps: name: 'Dashboard Apps' description: 'Dashboard Apps allow you to create and embed applications that display user information, orders, or payment history, providing more context to your customer support agents.' dyte: name: 'Dyte' + short_description: 'Start video/voice calls with customers directly from Chatwoot.' description: 'Dyte is a product that integrates audio and video functionalities into your application. With this integration, your agents can start video/voice calls with your customers directly from Chatwoot.' meeting_name: '%{agent_name} has started a meeting' slack: name: 'Slack' + short_description: 'Receive notifications and respond to conversations directly in Slack.' description: "Integrate Chatwoot with Slack to keep your team in sync. This integration allows you to receive notifications for new conversations and respond to them directly within Slack's interface." webhooks: name: 'Webhooks' description: 'Webhook events provide real-time updates about activities in your Chatwoot account. You can subscribe to your preferred events, and Chatwoot will send you HTTP callbacks with the updates.' dialogflow: name: 'Dialogflow' + short_description: 'Build chatbots to handle initial queries before transferring to agents.' description: 'Build chatbots with Dialogflow and easily integrate them into your inbox. These bots can handle initial queries before transferring them to a customer service agent.' google_translate: name: 'Google Translate' + short_description: 'Automatically translate customer messages for agents.' description: "Integrate Google Translate to help agents easily translate customer messages. This integration automatically detects the language and converts it to the agent's or admin's preferred language." openai: name: 'OpenAI' + short_description: 'AI-powered reply suggestions, summarization, and message enhancement.' description: 'Leverage the power of large language models from OpenAI with the features such as reply suggestions, summarization, message rephrasing, spell-checking, and label classification.' linear: name: 'Linear' + short_description: 'Create and link Linear issues directly from conversations.' description: 'Create issues in Linear directly from your conversation window. Alternatively, link existing Linear issues for a more streamlined and efficient issue tracking process.' shopify: name: 'Shopify' + short_description: 'Access order details and customer data from your Shopify store.' description: 'Connect your Shopify store to access order details, customer information, and product data directly within your conversations and helps your support team provide faster, more contextual assistance to your customers.' leadsquared: name: 'LeadSquared' diff --git a/config/routes.rb b/config/routes.rb index d53efa0bb..be677bbe8 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -60,8 +60,8 @@ Rails.application.routes.draw do end resources :assistant_responses resources :bulk_actions, only: [:create] - resources :copilot_threads, only: [:index] do - resources :copilot_messages, only: [:index] + resources :copilot_threads, only: [:index, :create] do + resources :copilot_messages, only: [:index, :create] end resources :documents, only: [:index, :show, :create, :destroy] end @@ -137,6 +137,7 @@ Rails.application.routes.draw do get :conversations get :messages get :contacts + get :articles end end @@ -396,6 +397,7 @@ Rails.application.routes.draw do resources :users, only: [:create, :show, :update, :destroy] do member do get :login + post :token end end resources :agent_bots, only: [:index, :create, :show, :update, :destroy] do diff --git a/config/schedule.yml b/config/schedule.yml index 2747910ad..c45d395bf 100644 --- a/config/schedule.yml +++ b/config/schedule.yml @@ -39,3 +39,10 @@ process_stale_contacts_job: cron: '30 04 * * *' class: 'Internal::ProcessStaleContactsJob' queue: housekeeping + +# executed daily at 0100 UTC +# to delete accounts marked for deletion +delete_accounts_job: + cron: '0 1 * * *' + class: 'Internal::DeleteAccountsJob' + queue: scheduled_jobs diff --git a/db/migrate/20250523024825_remove_uuid_from_copilot_threads.rb b/db/migrate/20250523024825_remove_uuid_from_copilot_threads.rb new file mode 100644 index 000000000..c979f473b --- /dev/null +++ b/db/migrate/20250523024825_remove_uuid_from_copilot_threads.rb @@ -0,0 +1,8 @@ +class RemoveUuidFromCopilotThreads < ActiveRecord::Migration[7.1] + def change + remove_column :copilot_threads, :uuid, :string + + add_column :copilot_threads, :assistant_id, :integer + add_index :copilot_threads, :assistant_id + end +end diff --git a/db/migrate/20250523024826_remove_user_id_from_copilot_messages.rb b/db/migrate/20250523024826_remove_user_id_from_copilot_messages.rb new file mode 100644 index 000000000..0889095d6 --- /dev/null +++ b/db/migrate/20250523024826_remove_user_id_from_copilot_messages.rb @@ -0,0 +1,5 @@ +class RemoveUserIdFromCopilotMessages < ActiveRecord::Migration[7.1] + def change + remove_reference :copilot_messages, :user, index: true + end +end diff --git a/db/migrate/20250523031839_change_message_type_to_integer_in_copilot_messages.rb b/db/migrate/20250523031839_change_message_type_to_integer_in_copilot_messages.rb new file mode 100644 index 000000000..c3bed0bb3 --- /dev/null +++ b/db/migrate/20250523031839_change_message_type_to_integer_in_copilot_messages.rb @@ -0,0 +1,11 @@ +class ChangeMessageTypeToIntegerInCopilotMessages < ActiveRecord::Migration[7.1] + def up + remove_column :copilot_messages, :message_type + add_column :copilot_messages, :message_type, :integer, default: 0 + end + + def down + remove_column :copilot_messages, :message_type + add_column :copilot_messages, :message_type, :string, default: 'user' + end +end diff --git a/db/schema.rb b/db/schema.rb index 4ce0ec6b9..0f8ef41d8 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -577,27 +577,25 @@ ActiveRecord::Schema[7.0].define(version: 2025_05_26_000001) do create_table "copilot_messages", force: :cascade do |t| t.bigint "copilot_thread_id", null: false - t.bigint "user_id", null: false t.bigint "account_id", null: false - t.string "message_type", null: false t.jsonb "message", default: {}, null: false t.datetime "created_at", null: false t.datetime "updated_at", null: false + t.integer "message_type", default: 0 t.index ["account_id"], name: "index_copilot_messages_on_account_id" t.index ["copilot_thread_id"], name: "index_copilot_messages_on_copilot_thread_id" - t.index ["user_id"], name: "index_copilot_messages_on_user_id" end create_table "copilot_threads", force: :cascade do |t| t.string "title", null: false t.bigint "user_id", null: false t.bigint "account_id", null: false - t.uuid "uuid", default: -> { "gen_random_uuid()" }, null: false t.datetime "created_at", null: false t.datetime "updated_at", null: false + t.integer "assistant_id" t.index ["account_id"], name: "index_copilot_threads_on_account_id" + t.index ["assistant_id"], name: "index_copilot_threads_on_assistant_id" t.index ["user_id"], name: "index_copilot_threads_on_user_id" - t.index ["uuid"], name: "index_copilot_threads_on_uuid", unique: true end create_table "csat_survey_responses", force: :cascade do |t| diff --git a/deployment/chatwoot-web.1.service b/deployment/chatwoot-web.1.service index 049ec85db..0d1d33804 100644 --- a/deployment/chatwoot-web.1.service +++ b/deployment/chatwoot-web.1.service @@ -16,10 +16,10 @@ KillMode=mixed StandardInput=null SyslogIdentifier=%p -Environment="PATH=/home/chatwoot/.rvm/gems/ruby-3.3.3/bin:/home/chatwoot/.rvm/gems/ruby-3.3.3@global/bin:/home/chatwoot/.rvm/rubies/ruby-3.3.3/bin:/home/chatwoot/.rvm/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/snap/bin:/home/chatwoot/.rvm/bin:/home/chatwoot/.rvm/bin" +Environment="PATH=/home/chatwoot/.rvm/gems/ruby-3.4.4/bin:/home/chatwoot/.rvm/gems/ruby-3.4.4@global/bin:/home/chatwoot/.rvm/rubies/ruby-3.4.4/bin:/home/chatwoot/.rvm/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/snap/bin:/home/chatwoot/.rvm/bin:/home/chatwoot/.rvm/bin" Environment="PORT=3000" Environment="RAILS_ENV=production" Environment="NODE_ENV=production" Environment="RAILS_LOG_TO_STDOUT=true" -Environment="GEM_HOME=/home/chatwoot/.rvm/gems/ruby-3.3.3" -Environment="GEM_PATH=/home/chatwoot/.rvm/gems/ruby-3.3.3:/home/chatwoot/.rvm/gems/ruby-3.3.3@global" +Environment="GEM_HOME=/home/chatwoot/.rvm/gems/ruby-3.4.4" +Environment="GEM_PATH=/home/chatwoot/.rvm/gems/ruby-3.4.4:/home/chatwoot/.rvm/gems/ruby-3.4.4@global" diff --git a/deployment/chatwoot-worker.1.service b/deployment/chatwoot-worker.1.service index 04ee83a69..ea893d20c 100644 --- a/deployment/chatwoot-worker.1.service +++ b/deployment/chatwoot-worker.1.service @@ -21,10 +21,10 @@ MemoryHigh=1.4G MemorySwapMax=0 OOMPolicy=stop -Environment="PATH=/home/chatwoot/.rvm/gems/ruby-3.3.3/bin:/home/chatwoot/.rvm/gems/ruby-3.3.3@global/bin:/home/chatwoot/.rvm/rubies/ruby-3.3.3/bin:/home/chatwoot/.rvm/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/snap/bin:/home/chatwoot/.rvm/bin:/home/chatwoot/.rvm/bin" +Environment="PATH=/home/chatwoot/.rvm/gems/ruby-3.4.4/bin:/home/chatwoot/.rvm/gems/ruby-3.4.4@global/bin:/home/chatwoot/.rvm/rubies/ruby-3.4.4/bin:/home/chatwoot/.rvm/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/snap/bin:/home/chatwoot/.rvm/bin:/home/chatwoot/.rvm/bin" Environment="PORT=3000" Environment="RAILS_ENV=production" Environment="NODE_ENV=production" Environment="RAILS_LOG_TO_STDOUT=true" -Environment="GEM_HOME=/home/chatwoot/.rvm/gems/ruby-3.3.3" -Environment="GEM_PATH=/home/chatwoot/.rvm/gems/ruby-3.3.3:/home/chatwoot/.rvm/gems/ruby-3.3.3@global" +Environment="GEM_HOME=/home/chatwoot/.rvm/gems/ruby-3.4.4" +Environment="GEM_PATH=/home/chatwoot/.rvm/gems/ruby-3.4.4:/home/chatwoot/.rvm/gems/ruby-3.4.4@global" diff --git a/deployment/setup_20.04.sh b/deployment/setup_20.04.sh index 7809b6d0e..75320bbd3 100644 --- a/deployment/setup_20.04.sh +++ b/deployment/setup_20.04.sh @@ -338,8 +338,8 @@ function setup_chatwoot() { sudo -i -u chatwoot << EOF rvm --version rvm autolibs disable - rvm install "ruby-3.3.3" - rvm use 3.3.3 --default + rvm install "ruby-3.4.4" + rvm use 3.4.4 --default git clone https://github.com/chatwoot/chatwoot.git cd chatwoot diff --git a/developer-docs/README.md b/developer-docs/README.md new file mode 100644 index 000000000..d9426cd19 --- /dev/null +++ b/developer-docs/README.md @@ -0,0 +1,28 @@ +## Chatwoot Developer Documentation + +Welcome to the official Chatwoot developer documentation. This guide contains everything you need to know about Chatwoot APIs and build custom flows on top of Chatwoot APIs. + +### 👩💻 Development + +Install the [Mintlify CLI](https://www.npmjs.com/package/mintlify) to preview the documentation changes locally. To install, use the following command + +``` +npm i -g mintlify +``` + +Run the following command at the root of your documentation (where mint.json is) + +``` +mintlify dev +``` + +### 😎 Publishing Changes + +Changes will be deployed to production automatically after pushing to the default branch. + +You can also preview changes using PRs, which generates a preview link of the docs. + +#### Troubleshooting + +- Mintlify dev isn't running - Run `mintlify install` it'll re-install dependencies. +- Page loads as a 404 - Make sure you are running in a folder with `mint.json` diff --git a/developer-docs/docs.json b/developer-docs/docs.json new file mode 100644 index 000000000..a2ff9d1f6 --- /dev/null +++ b/developer-docs/docs.json @@ -0,0 +1,100 @@ +{ + "$schema": "https://mintlify.com/docs.json", + "name": "Chatwoot Developer Docs", + "description": "Official developer documentation for Chatwoot - the open-source customer support platform. Learn about our APIs, integrations, and development guidelines.", + "logo": { + "dark": "/logo/dark.png", + "light": "/logo/light.png" + }, + "favicon": "/favicon.png", + "colors": { + "primary": "#0069ED", + "light": "#4D9CFF", + "dark": "#0050B4" + }, + "fonts": { + "heading": { + "family": "Haskoy", + "weight": 500, + "source": "https://d1j5ayohogpcd2.cloudfront.net/fonts/haskoy/Haskoy-Medium.woff2", + "format": "woff2" + }, + "body": { + "family": "Haskoy", + "weight": 400, + "source": "https://d1j5ayohogpcd2.cloudfront.net/fonts/haskoy/Haskoy-Regular.woff2", + "format": "woff2" + } + }, + "theme": "maple", + "navigation": { + "groups": [ + { + "group": "API Reference", + "pages": [ + "introduction" + ] + }, + { + "group": "Application API", + "description": "APIs for managing application aspects of Chatwoot", + "includeTags": [ + "Agents", + "Automation Rule", + "Account AgentBots", + "Canned Responses", + "Contact Labels", + "Contacts", + "Conversation Assignments", + "Conversation Labels", + "Conversations", + "Custom Attributes", + "Custom Filters", + "Help Center", + "Inboxes", + "Integrations", + "Messages", + "Profile", + "Reports", + "Teams", + "Webhooks" + ], + "openapi": "https://raw.githubusercontent.com/chatwoot/chatwoot/develop/swagger/tag_groups/application_swagger.json" + }, + { + "group": "Platform API", + "description": "APIs for managing platform aspects of Chatwoot", + "includeTags": [ + "Accounts", + "Account Users", + "AgentBots", + "Users" + ], + "openapi": "https://raw.githubusercontent.com/chatwoot/chatwoot/develop/swagger/tag_groups/platform_swagger.json" + }, + { + "group": "Client API", + "description": "APIs for client applications", + "includeTags": [ + "Contacts API", + "Conversations API", + "Messages API" + ], + "openapi": "https://raw.githubusercontent.com/chatwoot/chatwoot/develop/swagger/tag_groups/client_swagger.json" + }, + { + "group": "Other APIs", + "description": "Other Chatwoot APIs", + "includeTags": [ + "CSAT Survey Page" + ], + "openapi": "https://raw.githubusercontent.com/chatwoot/chatwoot/develop/swagger/tag_groups/other_swagger.json" + } + ] + }, + "footerSocials": { + "twitter": "https://twitter.com/chatwootapp", + "github": "https://github.com/chatwoot", + "linkedin": "https://www.linkedin.com/company/chatwoot" + } +} diff --git a/developer-docs/favicon.png b/developer-docs/favicon.png new file mode 100644 index 000000000..4b7824e37 Binary files /dev/null and b/developer-docs/favicon.png differ diff --git a/developer-docs/introduction.mdx b/developer-docs/introduction.mdx new file mode 100644 index 000000000..28f8436ea --- /dev/null +++ b/developer-docs/introduction.mdx @@ -0,0 +1,49 @@ +--- +title: Introduction to Chatwoot APIs +description: Learn how to use Chatwoot APIs to build integrations, customize chat experiences, and manage your installation. +sidebarTitle: Introduction +--- + +Welcome to the Chatwoot API documentation. Whether you're building custom workflows for your support team, integrating Chatwoot into your product, or managing users across installations, our APIs provide the flexibility and power to help you do more with Chatwoot. + +Chatwoot provides three categories of APIs, each designed with a specific use case in mind: + +- **Application APIs** – For account-level automation and agent-facing integrations. +- **Client APIs** – For building custom chat interfaces for end-users +- **Platform APIs** – For managing and administering installations at scale + +--- + +## Application APIs + +Application APIs are designed for interacting with a Chatwoot account from an agent/admin perspective. Use them to build internal tools, automate workflows, or perform bulk operations like data import/export. + +- **Authentication**: Requires a user `access_token`, which can be generated from **Profile Settings** after logging into your Chatwoot account. +- **Availability**: Supported on both **Cloud** and **Self-hosted** Chatwoot installations. +- **Example**: [Google Cloud Functions Demo](https://github.com/chatwoot/google-cloud-functions-demo) + +--- + +## Client APIs + +Client APIs are intended for building custom messaging experiences over Chatwoot. If you're not using the native website widget or want to embed chat in your mobile app, these APIs are the way to go. + +- **Authentication**: Uses `inbox_identifier` (from **Settings → Configuration** in API inboxes) and `contact_identifier` (returned when creating a contact). +- **Availability**: Supported on both **Cloud** and **Self-hosted** Chatwoot installations. +- **Examples**: + + - [Client API Demo](https://github.com/chatwoot/client-api-demo) + - [Flutter SDK](https://github.com/chatwoot/chatwoot-flutter-sdk) + +--- + +## Platform APIs + +Platform APIs are used to manage Chatwoot installations at the admin level. These APIs allow you to control users, roles, and accounts, or sync data from external authentication systems. + +- **Authentication**: Requires an `access_token` generated by a **Platform App**, which can be created in the **Super Admin Console**. +- **Availability**: Available on **Self-hosted** / **Managed Hosting** Chatwoot installations only. + +--- + +Use the right API for your use case, and you'll be able to extend, customize, and integrate Chatwoot into your stack with ease. diff --git a/developer-docs/logo/dark.png b/developer-docs/logo/dark.png new file mode 100644 index 000000000..9af20120e Binary files /dev/null and b/developer-docs/logo/dark.png differ diff --git a/developer-docs/logo/light.png b/developer-docs/logo/light.png new file mode 100644 index 000000000..86d75cf5f Binary files /dev/null and b/developer-docs/logo/light.png differ diff --git a/docker/Dockerfile b/docker/Dockerfile index 008884ce1..2d753337d 100644 --- a/docker/Dockerfile +++ b/docker/Dockerfile @@ -1,6 +1,6 @@ # pre-build stage FROM node:23-alpine as node -FROM ruby:3.3.3-alpine3.19 AS pre-builder +FROM ruby:3.4.4-alpine3.21 AS pre-builder ARG NODE_VERSION="23.7.0" ARG PNPM_VERSION="10.2.0" @@ -90,13 +90,13 @@ RUN if [ "$RAILS_ENV" = "production" ]; then \ RUN git rev-parse HEAD > /app/.git_sha # Remove unnecessary files -RUN rm -rf /gems/ruby/3.3.0/cache/*.gem \ - && find /gems/ruby/3.3.0/gems/ \( -name "*.c" -o -name "*.o" \) -delete \ +RUN rm -rf /gems/ruby/3.4.0/cache/*.gem \ + && find /gems/ruby/3.4.0/gems/ \( -name "*.c" -o -name "*.o" \) -delete \ && rm -rf .git \ && rm .gitignore # final build stage -FROM ruby:3.3.3-alpine3.19 +FROM ruby:3.4.4-alpine3.21 ARG NODE_VERSION="23.7.0" ARG PNPM_VERSION="10.2.0" diff --git a/enterprise/app/controllers/api/v1/accounts/captain/assistants_controller.rb b/enterprise/app/controllers/api/v1/accounts/captain/assistants_controller.rb index 35b6ffe1d..e5a055836 100644 --- a/enterprise/app/controllers/api/v1/accounts/captain/assistants_controller.rb +++ b/enterprise/app/controllers/api/v1/accounts/captain/assistants_controller.rb @@ -47,7 +47,7 @@ class Api::V1::Accounts::Captain::AssistantsController < Api::V1::Accounts::Base config: [ :product_name, :feature_faq, :feature_memory, :welcome_message, :handoff_message, :resolution_message, - :instructions + :instructions, :temperature ]) end diff --git a/enterprise/app/controllers/api/v1/accounts/captain/copilot_messages_controller.rb b/enterprise/app/controllers/api/v1/accounts/captain/copilot_messages_controller.rb index 2a30fba48..75abc0cdc 100644 --- a/enterprise/app/controllers/api/v1/accounts/captain/copilot_messages_controller.rb +++ b/enterprise/app/controllers/api/v1/accounts/captain/copilot_messages_controller.rb @@ -1,21 +1,29 @@ class Api::V1::Accounts::Captain::CopilotMessagesController < Api::V1::Accounts::BaseController - before_action :current_account - before_action -> { check_authorization(Captain::Assistant) } before_action :set_copilot_thread def index @copilot_messages = @copilot_thread .copilot_messages + .includes(:copilot_thread) .order(created_at: :asc) .page(permitted_params[:page] || 1) .per(1000) end + def create + @copilot_message = @copilot_thread.copilot_messages.create!( + message: { content: params[:message] }, + message_type: :user + ) + @copilot_message.enqueue_response_job(params[:conversation_id], Current.user.id) + end + private def set_copilot_thread @copilot_thread = Current.account.copilot_threads.find_by!( - uuid: params[:copilot_thread_id], user_id: Current.user.id + id: params[:copilot_thread_id], + user: Current.user ) end diff --git a/enterprise/app/controllers/api/v1/accounts/captain/copilot_threads_controller.rb b/enterprise/app/controllers/api/v1/accounts/captain/copilot_threads_controller.rb index e313f448c..533859b95 100644 --- a/enterprise/app/controllers/api/v1/accounts/captain/copilot_threads_controller.rb +++ b/enterprise/app/controllers/api/v1/accounts/captain/copilot_threads_controller.rb @@ -1,18 +1,46 @@ class Api::V1::Accounts::Captain::CopilotThreadsController < Api::V1::Accounts::BaseController - before_action :current_account - before_action -> { check_authorization(Captain::Assistant) } + before_action :ensure_message, only: :create def index @copilot_threads = Current.account.copilot_threads .where(user_id: Current.user.id) - .includes(:user) + .includes(:user, :assistant) .order(created_at: :desc) .page(permitted_params[:page] || 1) .per(5) end + def create + ActiveRecord::Base.transaction do + @copilot_thread = Current.account.copilot_threads.create!( + title: copilot_thread_params[:message], + user: Current.user, + assistant: assistant + ) + + copilot_message = @copilot_thread.copilot_messages.create!( + message_type: :user, + message: { content: copilot_thread_params[:message] } + ) + + copilot_message.enqueue_response_job(copilot_thread_params[:conversation_id], Current.user.id) + end + end + private + def ensure_message + return render_could_not_create_error('Message is required') if copilot_thread_params[:message].blank? + end + + def assistant + Current.account.captain_assistants.find(copilot_thread_params[:assistant_id]) + end + + def copilot_thread_params + params.permit(:message, :assistant_id, :conversation_id) + end + def permitted_params params.permit(:page) end diff --git a/enterprise/app/controllers/api/v1/accounts/custom_roles_controller.rb b/enterprise/app/controllers/api/v1/accounts/custom_roles_controller.rb index 01650a7de..2d6823b90 100644 --- a/enterprise/app/controllers/api/v1/accounts/custom_roles_controller.rb +++ b/enterprise/app/controllers/api/v1/accounts/custom_roles_controller.rb @@ -6,12 +6,12 @@ class Api::V1::Accounts::CustomRolesController < Api::V1::Accounts::EnterpriseAc @custom_roles = Current.account.custom_roles end + def show; end + def create @custom_role = Current.account.custom_roles.create!(permitted_params) end - def show; end - def update @custom_role.update!(permitted_params) end diff --git a/enterprise/app/controllers/api/v1/accounts/sla_policies_controller.rb b/enterprise/app/controllers/api/v1/accounts/sla_policies_controller.rb index e64256bc7..ec879b042 100644 --- a/enterprise/app/controllers/api/v1/accounts/sla_policies_controller.rb +++ b/enterprise/app/controllers/api/v1/accounts/sla_policies_controller.rb @@ -6,12 +6,12 @@ class Api::V1::Accounts::SlaPoliciesController < Api::V1::Accounts::EnterpriseAc @sla_policies = Current.account.sla_policies end + def show; end + def create @sla_policy = Current.account.sla_policies.create!(permitted_params) end - def show; end - def update @sla_policy.update!(permitted_params) end diff --git a/enterprise/app/controllers/enterprise/api/v1/accounts/conversations_controller.rb b/enterprise/app/controllers/enterprise/api/v1/accounts/conversations_controller.rb index c382206b6..cdea79064 100644 --- a/enterprise/app/controllers/enterprise/api/v1/accounts/conversations_controller.rb +++ b/enterprise/app/controllers/enterprise/api/v1/accounts/conversations_controller.rb @@ -19,9 +19,9 @@ module Enterprise::Api::V1::Accounts::ConversationsController response = Captain::Copilot::ChatService.new( assistant, - previous_messages: copilot_params[:previous_messages], - conversation_history: @conversation.to_llm_text, - language: @conversation.account.locale_english_name + previous_history: copilot_params[:previous_history], + conversation_id: @conversation.display_id, + user_id: Current.user.id ).generate_response(copilot_params[:message]) render json: { message: response['response'] } @@ -44,6 +44,6 @@ module Enterprise::Api::V1::Accounts::ConversationsController private def copilot_params - params.permit(:previous_messages, :message, :assistant_id) + params.permit(:previous_history, :message, :assistant_id) end end diff --git a/enterprise/app/controllers/enterprise/super_admin/app_configs_controller.rb b/enterprise/app/controllers/enterprise/super_admin/app_configs_controller.rb index f0b798648..2454295dc 100644 --- a/enterprise/app/controllers/enterprise/super_admin/app_configs_controller.rb +++ b/enterprise/app/controllers/enterprise/super_admin/app_configs_controller.rb @@ -33,6 +33,6 @@ module Enterprise::SuperAdmin::AppConfigsController def internal_config_options %w[CHATWOOT_INBOX_TOKEN CHATWOOT_INBOX_HMAC_KEY ANALYTICS_TOKEN CLEARBIT_API_KEY DASHBOARD_SCRIPTS INACTIVE_WHATSAPP_NUMBERS BLOCKED_EMAIL_DOMAINS - CAPTAIN_CLOUD_PLAN_LIMITS ACCOUNT_SECURITY_NOTIFICATION_WEBHOOK_URL] + CAPTAIN_CLOUD_PLAN_LIMITS ACCOUNT_SECURITY_NOTIFICATION_WEBHOOK_URL CHATWOOT_INSTANCE_ADMIN_EMAIL] end end diff --git a/enterprise/app/helpers/captain/chat_helper.rb b/enterprise/app/helpers/captain/chat_helper.rb index 146e8f813..924f11269 100644 --- a/enterprise/app/helpers/captain/chat_helper.rb +++ b/enterprise/app/helpers/captain/chat_helper.rb @@ -1,47 +1,34 @@ module Captain::ChatHelper - def search_documentation_tool - { - type: 'function', - function: { - name: 'search_documentation', - description: "Use this function to get documentation on functionalities you don't know about.", - parameters: { - type: 'object', - properties: { - search_query: { - type: 'string', - description: 'The search query to look up in the documentation.' - } - }, - required: ['search_query'] - } - } - } - end - def request_chat_completion - Rails.logger.debug { "[CAPTAIN][ChatCompletion] #{@messages}" } + log_chat_completion_request response = @client.chat( parameters: { model: @model, messages: @messages, - tools: [search_documentation_tool], - response_format: { type: 'json_object' } + tools: @tool_registry&.registered_tools || [], + response_format: { type: 'json_object' }, + temperature: @assistant&.config&.[]('temperature').to_f || 1 } ) handle_response(response) - @response + rescue StandardError => e + Rails.logger.error "#{self.class.name} Assistant: #{@assistant.id}, Error in chat completion: #{e}" + raise e end + private + def handle_response(response) - Rails.logger.debug { "[CAPTAIN][ChatCompletion] #{response}" } + Rails.logger.debug { "#{self.class.name} Assistant: #{@assistant.id}, Received response #{response}" } message = response.dig('choices', 0, 'message') if message['tool_calls'] process_tool_calls(message['tool_calls']) else - @response = JSON.parse(message['content'].strip) + message = JSON.parse(message['content'].strip) + persist_message(message, 'assistant') + message end end @@ -54,38 +41,22 @@ module Captain::ChatHelper end def process_tool_call(tool_call) + arguments = JSON.parse(tool_call['function']['arguments']) + function_name = tool_call['function']['name'] tool_call_id = tool_call['id'] - if tool_call['function']['name'] == 'search_documentation' - query = JSON.parse(tool_call['function']['arguments'])['search_query'] - sections = fetch_documentation(query) - append_tool_response(sections, tool_call_id) + if @tool_registry.respond_to?(function_name) + execute_tool(function_name, arguments, tool_call_id) else - append_tool_response('', tool_call_id) + process_invalid_tool_call(function_name, tool_call_id) end end - def fetch_documentation(query) - Rails.logger.debug { "[CAPTAIN][DocumentationSearch] #{query}" } - @assistant - .responses - .approved - .search(query) - .map { |response| format_response(response) }.join - end - - def format_response(response) - formatted_response = " - Question: #{response.question} - Answer: #{response.answer} - " - if response.documentable.present? && response.documentable.try(:external_link) - formatted_response += " - Source: #{response.documentable.external_link} - " - end - - formatted_response + def execute_tool(function_name, arguments, tool_call_id) + persist_message({ content: "Using tool #{function_name}", function_name: function_name }, 'assistant_thinking') + result = @tool_registry.send(function_name, arguments) + persist_message({ content: "Completed #{function_name} tool call", function_name: function_name }, 'assistant_thinking') + append_tool_response(result, tool_call_id) end def append_tool_calls(tool_calls) @@ -95,11 +66,24 @@ module Captain::ChatHelper } end - def append_tool_response(sections, tool_call_id) + def process_invalid_tool_call(function_name, tool_call_id) + persist_message({ content: 'Invalid tool call', function_name: function_name }, 'assistant_thinking') + append_tool_response('Tool not available', tool_call_id) + end + + def append_tool_response(content, tool_call_id) @messages << { role: 'tool', tool_call_id: tool_call_id, - content: "Found the following FAQs in the documentation:\n #{sections}" + content: content } end + + def log_chat_completion_request + Rails.logger.info( + "#{self.class.name} Assistant: #{@assistant.id}, Requesting chat completion + for messages #{@messages} with #{@tool_registry&.registered_tools&.length || 0} tools + " + ) + end end diff --git a/enterprise/app/helpers/super_admin/features.yml b/enterprise/app/helpers/super_admin/features.yml index 3767d6468..c20de2dfa 100644 --- a/enterprise/app/helpers/super_admin/features.yml +++ b/enterprise/app/helpers/super_admin/features.yml @@ -48,6 +48,12 @@ messenger: enabled: true icon: 'icon-messenger-line' config_key: 'facebook' +instagram: + name: 'Instagram' + description: 'Stay connected with your customers on Instagram' + enabled: true + icon: 'icon-instagram' + config_key: 'instagram' whatsapp: name: 'WhatsApp' description: 'Manage your WhatsApp business interactions from Chatwoot.' @@ -81,19 +87,19 @@ microsoft: config_key: 'microsoft' linear: name: 'Linear' - description: 'Configuration for setting up Linear' + description: 'Configuration for setting up Linear Integration' enabled: true icon: 'icon-linear' config_key: 'linear' -instagram: - name: 'Instagram' - description: 'Configuration for setting up Instagram' +slack: + name: 'Slack' + description: 'Configuration for setting up Slack Integration' enabled: true - icon: 'icon-instagram' - config_key: 'instagram' + icon: 'icon-slack' + config_key: 'slack' shopify: name: 'Shopify' - description: 'Configuration for setting up Shopify' + description: 'Configuration for setting up Shopify Integration' enabled: true icon: 'icon-shopify' config_key: 'shopify' diff --git a/enterprise/app/jobs/captain/conversation/response_builder_job.rb b/enterprise/app/jobs/captain/conversation/response_builder_job.rb index 39a12b662..c661caebe 100644 --- a/enterprise/app/jobs/captain/conversation/response_builder_job.rb +++ b/enterprise/app/jobs/captain/conversation/response_builder_job.rb @@ -50,7 +50,7 @@ class Captain::Conversation::ResponseBuilderJob < ApplicationJob def message_content(message) return message.content if message.content.present? - 'User has shared an attachment' if message.attachments.any? + return 'User has shared an attachment' if message.attachments.any? 'User has shared a message without content' end diff --git a/enterprise/app/jobs/captain/copilot/response_job.rb b/enterprise/app/jobs/captain/copilot/response_job.rb new file mode 100644 index 000000000..fca5b0cce --- /dev/null +++ b/enterprise/app/jobs/captain/copilot/response_job.rb @@ -0,0 +1,25 @@ +class Captain::Copilot::ResponseJob < ApplicationJob + queue_as :default + + def perform(assistant:, conversation_id:, user_id:, copilot_thread_id:, message:) + Rails.logger.info("#{self.class.name} Copilot response job for assistant_id=#{assistant.id} user_id=#{user_id}") + generate_chat_response( + assistant: assistant, + conversation_id: conversation_id, + user_id: user_id, + copilot_thread_id: copilot_thread_id, + message: message + ) + end + + private + + def generate_chat_response(assistant:, conversation_id:, user_id:, copilot_thread_id:, message:) + Captain::Copilot::ChatService.new( + assistant, + user_id: user_id, + copilot_thread_id: copilot_thread_id, + conversation_id: conversation_id + ).generate_response(message) + end +end diff --git a/enterprise/listeners/captain_listener.rb b/enterprise/app/listeners/captain_listener.rb similarity index 94% rename from enterprise/listeners/captain_listener.rb rename to enterprise/app/listeners/captain_listener.rb index adf156562..879fb2b7e 100644 --- a/enterprise/listeners/captain_listener.rb +++ b/enterprise/app/listeners/captain_listener.rb @@ -1,7 +1,10 @@ class CaptainListener < BaseListener + include ::Events::Types + def conversation_resolved(event) conversation = extract_conversation_and_account(event)[0] assistant = conversation.inbox.captain_assistant + return unless conversation.inbox.captain_active? Captain::Llm::ContactNotesService.new(assistant, conversation).generate_and_update_notes if assistant.config['feature_memory'].present? diff --git a/enterprise/app/listeners/enterprise/action_cable_listener.rb b/enterprise/app/listeners/enterprise/action_cable_listener.rb new file mode 100644 index 000000000..fbeee170a --- /dev/null +++ b/enterprise/app/listeners/enterprise/action_cable_listener.rb @@ -0,0 +1,11 @@ +module Enterprise::ActionCableListener + include Events::Types + def copilot_message_created(event) + copilot_message = event.data[:copilot_message] + copilot_thread = copilot_message.copilot_thread + account = copilot_thread.account + user = copilot_thread.user + + broadcast(account, [user.pubsub_token], COPILOT_MESSAGE_CREATED, copilot_message.push_event_data) + end +end diff --git a/enterprise/app/models/captain/assistant.rb b/enterprise/app/models/captain/assistant.rb index ad441ec28..40cf99df9 100644 --- a/enterprise/app/models/captain/assistant.rb +++ b/enterprise/app/models/captain/assistant.rb @@ -29,6 +29,7 @@ class Captain::Assistant < ApplicationRecord has_many :inboxes, through: :captain_inboxes has_many :messages, as: :sender, dependent: :nullify + has_many :copilot_threads, dependent: :destroy_async validates :name, presence: true validates :description, presence: true diff --git a/enterprise/app/models/copilot_message.rb b/enterprise/app/models/copilot_message.rb index 16ae2c3c9..5941656c3 100644 --- a/enterprise/app/models/copilot_message.rb +++ b/enterprise/app/models/copilot_message.rb @@ -4,24 +4,65 @@ # # id :bigint not null, primary key # message :jsonb not null -# message_type :string not null +# message_type :integer default("user") # created_at :datetime not null # updated_at :datetime not null # account_id :bigint not null # copilot_thread_id :bigint not null -# user_id :bigint not null # # Indexes # # index_copilot_messages_on_account_id (account_id) # index_copilot_messages_on_copilot_thread_id (copilot_thread_id) -# index_copilot_messages_on_user_id (user_id) # class CopilotMessage < ApplicationRecord belongs_to :copilot_thread - belongs_to :user belongs_to :account - validates :message_type, presence: true, inclusion: { in: %w[user assistant assistant_thinking] } + enum message_type: { user: 0, assistant: 1, assistant_thinking: 2 } + + validates :message_type, presence: true validates :message, presence: true + before_validation :ensure_account + validate :validate_message_attributes + after_create_commit :broadcast_message + + def push_event_data + { + id: id, + message: message, + message_type: message_type, + created_at: created_at.to_i, + copilot_thread: copilot_thread.push_event_data + } + end + + def enqueue_response_job(conversation_id, user_id) + Captain::Copilot::ResponseJob.perform_later( + assistant: copilot_thread.assistant, + conversation_id: conversation_id, + user_id: user_id, + copilot_thread_id: copilot_thread.id, + message: message['content'] + ) + end + + private + + def ensure_account + self.account_id = copilot_thread&.account_id + end + + def broadcast_message + Rails.configuration.dispatcher.dispatch(COPILOT_MESSAGE_CREATED, Time.zone.now, copilot_message: self) + end + + def validate_message_attributes + return if message.blank? + + allowed_keys = %w[content reasoning function_name] + invalid_keys = message.keys - allowed_keys + + errors.add(:message, "contains invalid attributes: #{invalid_keys.join(', ')}") if invalid_keys.any? + end end diff --git a/enterprise/app/models/copilot_thread.rb b/enterprise/app/models/copilot_thread.rb index 865418ad7..c3d8a1821 100644 --- a/enterprise/app/models/copilot_thread.rb +++ b/enterprise/app/models/copilot_thread.rb @@ -2,25 +2,47 @@ # # Table name: copilot_threads # -# id :bigint not null, primary key -# title :string not null -# uuid :uuid not null -# created_at :datetime not null -# updated_at :datetime not null -# account_id :bigint not null -# user_id :bigint not null +# id :bigint not null, primary key +# title :string not null +# created_at :datetime not null +# updated_at :datetime not null +# account_id :bigint not null +# assistant_id :integer +# user_id :bigint not null # # Indexes # -# index_copilot_threads_on_account_id (account_id) -# index_copilot_threads_on_user_id (user_id) -# index_copilot_threads_on_uuid (uuid) UNIQUE +# index_copilot_threads_on_account_id (account_id) +# index_copilot_threads_on_assistant_id (assistant_id) +# index_copilot_threads_on_user_id (user_id) # class CopilotThread < ApplicationRecord belongs_to :user belongs_to :account - has_many :copilot_messages, dependent: :destroy + belongs_to :assistant, class_name: 'Captain::Assistant' + has_many :copilot_messages, dependent: :destroy_async validates :title, presence: true - validates :uuid, presence: true, uniqueness: true + + def push_event_data + { + id: id, + title: title, + created_at: created_at.to_i, + user: user.push_event_data, + account_id: account_id + } + end + + def previous_history + copilot_messages + .where(message_type: %w[user assistant]) + .order(created_at: :asc) + .map do |copilot_message| + { + content: copilot_message.message['content'], + role: copilot_message.message_type + } + end + end end diff --git a/enterprise/app/models/enterprise/concerns/user.rb b/enterprise/app/models/enterprise/concerns/user.rb index 0e597b8d8..7231decb7 100644 --- a/enterprise/app/models/enterprise/concerns/user.rb +++ b/enterprise/app/models/enterprise/concerns/user.rb @@ -6,7 +6,6 @@ module Enterprise::Concerns::User has_many :captain_responses, class_name: 'Captain::AssistantResponse', dependent: :nullify, as: :documentable has_many :copilot_threads, dependent: :destroy_async - has_many :copilot_messages, dependent: :destroy_async end def ensure_installation_pricing_plan_quantity diff --git a/enterprise/app/services/captain/copilot/chat_service.rb b/enterprise/app/services/captain/copilot/chat_service.rb index 6fd3c4e68..701837f55 100644 --- a/enterprise/app/services/captain/copilot/chat_service.rb +++ b/enterprise/app/services/captain/copilot/chat_service.rb @@ -3,42 +3,110 @@ require 'openai' class Captain::Copilot::ChatService < Llm::BaseOpenAiService include Captain::ChatHelper + attr_reader :assistant, :account, :user, :copilot_thread, :previous_history, :messages + def initialize(assistant, config) super() @assistant = assistant - @conversation_history = config[:conversation_history] - @previous_messages = config[:previous_messages] || [] - @language = config[:language] || 'english' - @messages = [system_message, conversation_history_context] + @previous_messages - @response = '' + @account = assistant.account + @user = nil + @copilot_thread = nil + @previous_history = [] + setup_user(config) + setup_message_history(config) + register_tools + @messages = build_messages(config) end def generate_response(input) @messages << { role: 'user', content: input } if input.present? response = request_chat_completion - Rails.logger.info("[CAPTAIN][CopilotChatService] Incrementing response usage for #{@assistant.account.id}") - @assistant.account.increment_response_usage + + Rails.logger.debug { "#{self.class.name} Assistant: #{@assistant.id}, Received response #{response}" } + Rails.logger.info( + "#{self.class.name} Assistant: #{@assistant.id}, Incrementing response usage for account #{@account.id}" + ) + @account.increment_response_usage response end private + def setup_user(config) + @user = @account.users.find_by(id: config[:user_id]) if config[:user_id].present? + end + + def build_messages(config) + messages= [system_message] + messages << account_id_context + messages += @previous_history if @previous_history.present? + messages += current_viewing_history(config[:conversation_id]) if config[:conversation_id].present? + messages + end + + def setup_message_history(config) + Rails.logger.info( + "#{self.class.name} Assistant: #{@assistant.id}, Previous History: #{config[:previous_history]&.length || 0}, Language: #{config[:language]}" + ) + + @copilot_thread = @account.copilot_threads.find_by(id: config[:copilot_thread_id]) if config[:copilot_thread_id].present? + @previous_history = if @copilot_thread.present? + @copilot_thread.previous_history + else + config[:previous_history].presence || [] + end + end + + def register_tools + @tool_registry = Captain::ToolRegistryService.new(@assistant, user: @user) + @tool_registry.register_tool(Captain::Tools::SearchDocumentationService) + @tool_registry.register_tool(Captain::Tools::Copilot::GetArticleService) + @tool_registry.register_tool(Captain::Tools::Copilot::GetContactService) + @tool_registry.register_tool(Captain::Tools::Copilot::GetConversationService) + @tool_registry.register_tool(Captain::Tools::Copilot::SearchArticlesService) + @tool_registry.register_tool(Captain::Tools::Copilot::SearchContactsService) + @tool_registry.register_tool(Captain::Tools::Copilot::SearchConversationsService) + @tool_registry.register_tool(Captain::Tools::Copilot::SearchLinearIssuesService) + end + def system_message { role: 'system', - content: Captain::Llm::SystemPromptsService.copilot_response_generator(@assistant.config['product_name'], @language) + content: Captain::Llm::SystemPromptsService.copilot_response_generator(@assistant.config['product_name']) } end - def conversation_history_context + def account_id_context { role: 'system', - content: " - Message History with the user is below: - #{@conversation_history} - " + content: "The current account id is #{@account.id}. The account is using #{@account.locale_english_name} as the language." } end + + def current_viewing_history(conversation_id) + conversation = @account.conversations.find_by(display_id: conversation_id) + return [] unless conversation + + Rails.logger.info("#{self.class.name} Assistant: #{@assistant.id}, Setting viewing history for conversation_id=#{conversation_id}") + contact_id = conversation.contact_id + [{ + role: 'system', + content: <<~HISTORY.strip + You are currently viewing the conversation with the following details: + Conversation ID: #{conversation_id} + Contact ID: #{contact_id} + HISTORY + }] + end + + def persist_message(message, message_type = 'assistant') + return if @copilot_thread.blank? + + @copilot_thread.copilot_messages.create!( + message: message, + message_type: message_type + ) + end end diff --git a/enterprise/app/services/captain/llm/assistant_chat_service.rb b/enterprise/app/services/captain/llm/assistant_chat_service.rb index 50688f18b..569931d44 100644 --- a/enterprise/app/services/captain/llm/assistant_chat_service.rb +++ b/enterprise/app/services/captain/llm/assistant_chat_service.rb @@ -9,6 +9,7 @@ class Captain::Llm::AssistantChatService < Llm::BaseOpenAiService @assistant = assistant @messages = [system_message] @response = '' + register_tools end def generate_response(input, previous_messages = [], role = 'user') @@ -19,10 +20,19 @@ class Captain::Llm::AssistantChatService < Llm::BaseOpenAiService private + def register_tools + @tool_registry = Captain::ToolRegistryService.new(@assistant, user: nil) + @tool_registry.register_tool(Captain::Tools::SearchDocumentationService) + end + def system_message { role: 'system', content: Captain::Llm::SystemPromptsService.assistant_response_generator(@assistant.name, @assistant.config['product_name'], @assistant.config) } end + + def persist_message(message, message_type = 'assistant') + # No need to implement + end end diff --git a/enterprise/app/services/captain/llm/system_prompts_service.rb b/enterprise/app/services/captain/llm/system_prompts_service.rb index 2d2940a8e..f50ad0c6c 100644 --- a/enterprise/app/services/captain/llm/system_prompts_service.rb +++ b/enterprise/app/services/captain/llm/system_prompts_service.rb @@ -56,18 +56,18 @@ class Captain::Llm::SystemPromptsService SYSTEM_PROMPT_MESSAGE end - def copilot_response_generator(product_name, language) + def copilot_response_generator(product_name) <<~SYSTEM_PROMPT_MESSAGE [Identity] You are Captain, a helpful and friendly copilot assistant for support agents using the product #{product_name}. Your primary role is to assist support agents by retrieving information, compiling accurate responses, and guiding them through customer interactions. You should only provide information related to #{product_name} and must not address queries about other products or external events. [Context] - You will be provided with the message history between the support agent and the customer. Use this context to understand the conversation flow, identify unresolved queries, and ensure responses are relevant and consistent with previous interactions. Always maintain a coherent and professional tone throughout the conversation. + Identify unresolved queries, and ensure responses are relevant and consistent with previous interactions. Always maintain a coherent and professional tone throughout the conversation. [Response Guidelines] - Use natural, polite, and conversational language that is clear and easy to follow. Keep sentences short and use simple words. - - Reply in the language the agent is using, if you're not able to detect the language, reply in #{language}. + - Reply in the language the agent is using, if you're not able to detect the language. - Provide brief and relevant responses—typically one or two sentences unless a more detailed explanation is necessary. - Do not use your own training data or assumptions to answer queries. Base responses strictly on the provided information. - If the query is unclear, ask concise clarifying questions instead of making assumptions. diff --git a/enterprise/app/services/captain/tool_registry_service.rb b/enterprise/app/services/captain/tool_registry_service.rb new file mode 100644 index 000000000..8d6632e57 --- /dev/null +++ b/enterprise/app/services/captain/tool_registry_service.rb @@ -0,0 +1,30 @@ +class Captain::ToolRegistryService + attr_reader :registered_tools, :tools + + def initialize(assistant, user: nil) + @assistant = assistant + @user = user + @registered_tools = [] + @tools = {} + end + + def register_tool(tool_class) + tool = tool_class.new(@assistant, user: @user) + return unless tool.active? + + @tools[tool.name] = tool + @registered_tools << tool.to_registry_format + end + + def method_missing(method_name, *) + if @tools.key?(method_name.to_s) + @tools[method_name.to_s].execute(*) + else + super + end + end + + def respond_to_missing?(method_name, include_private = false) + @tools.key?(method_name.to_s) || super + end +end diff --git a/enterprise/app/services/captain/tools/base_service.rb b/enterprise/app/services/captain/tools/base_service.rb new file mode 100644 index 000000000..72fde2df8 --- /dev/null +++ b/enterprise/app/services/captain/tools/base_service.rb @@ -0,0 +1,53 @@ +class Captain::Tools::BaseService + attr_accessor :assistant + + def initialize(assistant, user: nil) + @assistant = assistant + @user = user + end + + def name + raise NotImplementedError, "#{self.class} must implement name" + end + + def description + raise NotImplementedError, "#{self.class} must implement description" + end + + def parameters + raise NotImplementedError, "#{self.class} must implement parameters" + end + + def execute(arguments) + raise NotImplementedError, "#{self.class} must implement execute" + end + + def to_registry_format + { + type: 'function', + function: { + name: name, + description: description, + parameters: parameters + } + } + end + + def active? + true + end + + private + + def user_has_permission(permission) + return false if @user.blank? + + account_user = AccountUser.find_by(account_id: @assistant.account_id, user_id: @user.id) + return false if account_user.blank? + + return account_user.custom_role.permissions.include?(permission) if account_user.custom_role.present? + + # Default permission for agents without custom roles + account_user.administrator? || account_user.agent? + end +end diff --git a/enterprise/app/services/captain/tools/copilot/get_article_service.rb b/enterprise/app/services/captain/tools/copilot/get_article_service.rb new file mode 100644 index 000000000..9c2ee02da --- /dev/null +++ b/enterprise/app/services/captain/tools/copilot/get_article_service.rb @@ -0,0 +1,39 @@ +class Captain::Tools::Copilot::GetArticleService < Captain::Tools::BaseService + def name + 'get_article' + end + + def description + 'Get details of an article including its content and metadata' + end + + def parameters + { + type: 'object', + properties: { + article_id: { + type: 'number', + description: 'The ID of the article to retrieve' + } + }, + required: %w[article_id] + } + end + + def execute(arguments) + article_id = arguments['article_id'] + + Rails.logger.info { "#{self.class.name}: Article ID: #{article_id}" } + + return 'Missing required parameters' if article_id.blank? + + article = Article.find_by(id: article_id, account_id: @assistant.account_id) + return 'Article not found' if article.nil? + + article.to_llm_text + end + + def active? + user_has_permission('knowledge_base_manage') + end +end diff --git a/enterprise/app/services/captain/tools/copilot/get_contact_service.rb b/enterprise/app/services/captain/tools/copilot/get_contact_service.rb new file mode 100644 index 000000000..290433301 --- /dev/null +++ b/enterprise/app/services/captain/tools/copilot/get_contact_service.rb @@ -0,0 +1,39 @@ +class Captain::Tools::Copilot::GetContactService < Captain::Tools::BaseService + def name + 'get_contact' + end + + def description + 'Get details of a contact including their profile information' + end + + def parameters + { + type: 'object', + properties: { + contact_id: { + type: 'number', + description: 'The ID of the contact to retrieve' + } + }, + required: %w[contact_id] + } + end + + def execute(arguments) + contact_id = arguments['contact_id'] + + Rails.logger.info "#{self.class.name}: Contact ID: #{contact_id}" + + return 'Missing required parameters' if contact_id.blank? + + contact = Contact.find_by(id: contact_id, account_id: @assistant.account_id) + return 'Contact not found' if contact.nil? + + contact.to_llm_text + end + + def active? + user_has_permission('contact_manage') + end +end diff --git a/enterprise/app/services/captain/tools/copilot/get_conversation_service.rb b/enterprise/app/services/captain/tools/copilot/get_conversation_service.rb new file mode 100644 index 000000000..64b52d012 --- /dev/null +++ b/enterprise/app/services/captain/tools/copilot/get_conversation_service.rb @@ -0,0 +1,41 @@ +class Captain::Tools::Copilot::GetConversationService < Captain::Tools::BaseService + def name + 'get_conversation' + end + + def description + 'Get details of a conversation including messages and contact information' + end + + def parameters + { + type: 'object', + properties: { + conversation_id: { + type: 'number', + description: 'The ID of the conversation to retrieve' + } + }, + required: %w[conversation_id] + } + end + + def execute(arguments) + conversation_id = arguments['conversation_id'] + + Rails.logger.info "#{self.class.name}: Conversation ID: #{conversation_id}" + + return 'Missing required parameters' if conversation_id.blank? + + conversation = Conversation.find_by(display_id: conversation_id, account_id: @assistant.account_id) + return 'Conversation not found' if conversation.blank? + + conversation.to_llm_text + end + + def active? + user_has_permission('conversation_manage') || + user_has_permission('conversation_unassigned_manage') || + user_has_permission('conversation_participating_manage') + end +end diff --git a/enterprise/app/services/captain/tools/copilot/search_articles_service.rb b/enterprise/app/services/captain/tools/copilot/search_articles_service.rb new file mode 100644 index 000000000..5061968ad --- /dev/null +++ b/enterprise/app/services/captain/tools/copilot/search_articles_service.rb @@ -0,0 +1,71 @@ +class Captain::Tools::Copilot::SearchArticlesService < Captain::Tools::BaseService + def name + 'search_articles' + end + + def description + 'Search articles based on parameters' + end + + def parameters + { + type: 'object', + properties: properties, + required: ['query'] + } + end + + def execute(arguments) + query = arguments['query'] + category_id = arguments['category_id'] + status = arguments['status'] + + Rails.logger.info "#{self.class.name}: Query: #{query}, Category ID: #{category_id}, Status: #{status}" + + return 'Missing required parameters' if query.blank? + + articles = fetch_articles(query, category_id, status) + + return 'No articles found' unless articles.exists? + + total_count = articles.count + articles = articles.limit(100) + + <<~RESPONSE + #{total_count > 100 ? "Found #{total_count} articles (showing first 100)" : "Total number of articles: #{total_count}"} + #{articles.map(&:to_llm_text).join("\n---\n")} + RESPONSE + end + + def active? + user_has_permission('knowledge_base_manage') + end + + private + + def fetch_articles(query, category_id, status) + articles = Article.where(account_id: @assistant.account_id) + articles = articles.where('title ILIKE :query OR content ILIKE :query', query: "%#{query}%") if query.present? + articles = articles.where(category_id: category_id) if category_id.present? + articles = articles.where(status: status) if status.present? + articles + end + + def properties + { + query: { + type: 'string', + description: 'Search articles by title or content (partial match)' + }, + category_id: { + type: 'number', + description: 'Filter articles by category ID' + }, + status: { + type: 'string', + enum: %w[draft published archived], + description: 'Filter articles by status' + } + } + end +end diff --git a/enterprise/app/services/captain/tools/copilot/search_contacts_service.rb b/enterprise/app/services/captain/tools/copilot/search_contacts_service.rb new file mode 100644 index 000000000..557fc731a --- /dev/null +++ b/enterprise/app/services/captain/tools/copilot/search_contacts_service.rb @@ -0,0 +1,61 @@ +class Captain::Tools::Copilot::SearchContactsService < Captain::Tools::BaseService + def name + 'search_contacts' + end + + def description + 'Search contacts based on query parameters' + end + + def parameters + { + type: 'object', + properties: properties, + required: [] + } + end + + def execute(arguments) + email = arguments['email'] + phone_number = arguments['phone_number'] + name = arguments['name'] + + Rails.logger.info "#{self.class.name} Email: #{email}, Phone Number: #{phone_number}, Name: #{name}" + + contacts = Contact.where(account_id: @assistant.account_id) + contacts = contacts.where(email: email) if email.present? + contacts = contacts.where(phone_number: phone_number) if phone_number.present? + contacts = contacts.where('LOWER(name) ILIKE ?', "%#{name.downcase}%") if name.present? + + return 'No contacts found' unless contacts.exists? + + contacts = contacts.limit(100) + + <<~RESPONSE + #{contacts.map(&:to_llm_text).join("\n---\n")} + RESPONSE + end + + def active? + user_has_permission('contact_manage') + end + + private + + def properties + { + email: { + type: 'string', + description: 'Filter contacts by email' + }, + phone_number: { + type: 'string', + description: 'Filter contacts by phone number' + }, + name: { + type: 'string', + description: 'Filter contacts by name (partial match)' + } + } + end +end diff --git a/enterprise/app/services/captain/tools/copilot/search_conversations_service.rb b/enterprise/app/services/captain/tools/copilot/search_conversations_service.rb new file mode 100644 index 000000000..f97604793 --- /dev/null +++ b/enterprise/app/services/captain/tools/copilot/search_conversations_service.rb @@ -0,0 +1,78 @@ +class Captain::Tools::Copilot::SearchConversationsService < Captain::Tools::BaseService + def name + 'search_conversations' + end + + def description + 'Search conversations based on parameters' + end + + def parameters + { + type: 'object', + properties: properties, + required: [] + } + end + + def execute(arguments) + status = arguments['status'] + contact_id = arguments['contact_id'] + priority = arguments['priority'] + + conversations = get_conversations(status, contact_id, priority) + + return 'No conversations found' unless conversations.exists? + + total_count = conversations.count + conversations = conversations.limit(100) + + <<~RESPONSE + #{total_count > 100 ? "Found #{total_count} conversations (showing first 100)" : "Total number of conversations: #{total_count}"} + #{conversations.map { |conversation| conversation.to_llm_text(include_contact_details: true) }.join("\n---\n")} + RESPONSE + end + + def active? + user_has_permission('conversation_manage') || + user_has_permission('conversation_unassigned_manage') || + user_has_permission('conversation_participating_manage') + end + + private + + def get_conversations(status, contact_id, priority) + conversations = permissible_conversations + conversations = conversations.where(contact_id: contact_id) if contact_id.present? + conversations = conversations.where(status: status) if status.present? + conversations = conversations.where(priority: priority) if priority.present? + conversations + end + + def permissible_conversations + Conversations::PermissionFilterService.new( + @assistant.account.conversations, + @user, + @assistant.account + ).perform + end + + def properties + { + contact_id: { + type: 'number', + description: 'Filter conversations by contact ID' + }, + status: { + type: 'string', + enum: %w[open resolved pending snoozed], + description: 'Filter conversations by status' + }, + priority: { + type: 'string', + enum: %w[low medium high urgent], + description: 'Filter conversations by priority' + } + } + end +end diff --git a/enterprise/app/services/captain/tools/copilot/search_linear_issues_service.rb b/enterprise/app/services/captain/tools/copilot/search_linear_issues_service.rb new file mode 100644 index 000000000..0d59e194d --- /dev/null +++ b/enterprise/app/services/captain/tools/copilot/search_linear_issues_service.rb @@ -0,0 +1,77 @@ +class Captain::Tools::Copilot::SearchLinearIssuesService < Captain::Tools::BaseService + def name + 'search_linear_issues' + end + + def description + 'Search Linear issues based on a search term' + end + + def parameters + { + type: 'object', + properties: { + term: { + type: 'string', + description: 'The search term to find Linear issues' + } + }, + required: %w[term] + } + end + + def execute(arguments) + return 'Linear integration is not enabled' unless active? + + term = arguments['term'] + + Rails.logger.info "#{self.class.name}: Service called with the search term #{term}" + + return 'Missing required parameters' if term.blank? + + linear_service = Integrations::Linear::ProcessorService.new(account: @assistant.account) + result = linear_service.search_issue(term) + + return result[:error] if result[:error] + + issues = result[:data] + return 'No issues found, I should try another similar search term' if issues.blank? + + total_count = issues.length + + <<~RESPONSE + Total number of issues: #{total_count} + #{issues.map { |issue| format_issue(issue) }.join("\n---\n")} + RESPONSE + end + + def active? + @user.present? && @assistant.account.hooks.exists?(app_id: 'linear') + end + + private + + def format_issue(issue) + <<~ISSUE + Title: #{issue['title']} + ID: #{issue['id']} + State: #{issue['state']['name']} + Priority: #{format_priority(issue['priority'])} + #{issue['assignee'] ? "Assignee: #{issue['assignee']['name']}" : 'Assignee: Unassigned'} + #{issue['description'].present? ? "\nDescription: #{issue['description']}" : ''} + ISSUE + end + + def format_priority(priority) + return 'No priority' if priority.nil? + + case priority + when 0 then 'No priority' + when 1 then 'Urgent' + when 2 then 'High' + when 3 then 'Medium' + when 4 then 'Low' + else 'Unknown' + end + end +end diff --git a/enterprise/app/services/captain/tools/search_documentation_service.rb b/enterprise/app/services/captain/tools/search_documentation_service.rb new file mode 100644 index 000000000..672baf24a --- /dev/null +++ b/enterprise/app/services/captain/tools/search_documentation_service.rb @@ -0,0 +1,49 @@ +class Captain::Tools::SearchDocumentationService < Captain::Tools::BaseService + def name + 'search_documentation' + end + + def description + 'Search and retrieve documentation from knowledge base' + end + + def parameters + { + type: 'object', + properties: { + search_query: { + type: 'string', + description: 'The search query to look up in the documentation.' + } + }, + required: ['search_query'] + } + end + + def execute(arguments) + query = arguments['search_query'] + Rails.logger.info { "#{self.class.name}: #{query}" } + + responses = assistant.responses.approved.search(query) + + return 'No FAQs found for the given query' if responses.empty? + + responses.map { |response| format_response(response) }.join + end + + private + + def format_response(response) + formatted_response = " + Question: #{response.question} + Answer: #{response.answer} + " + if response.documentable.present? && response.documentable.try(:external_link) + formatted_response += " + Source: #{response.documentable.external_link} + " + end + + formatted_response + end +end diff --git a/enterprise/app/services/enterprise/conversations/permission_filter_service.rb b/enterprise/app/services/enterprise/conversations/permission_filter_service.rb index 5da65ffa6..f55265a90 100644 --- a/enterprise/app/services/enterprise/conversations/permission_filter_service.rb +++ b/enterprise/app/services/enterprise/conversations/permission_filter_service.rb @@ -1,36 +1,37 @@ module Enterprise::Conversations::PermissionFilterService def perform - account_user = AccountUser.find_by(account_id: account.id, user_id: user.id) - permissions = account_user&.permissions || [] - user_role = account_user&.role + return filter_by_permissions(permissions) if user_has_custom_role? - # Skip filtering for administrators - return conversations if user_role == 'administrator' - # Skip filtering for regular agents (without custom roles/permissions) - return conversations if user_role == 'agent' && account_user&.custom_role_id.nil? - - filter_by_permissions(permissions) + super end private + def user_has_custom_role? + user_role == 'agent' && account_user&.custom_role_id.present? + end + + def permissions + account_user&.permissions || [] + end + def filter_by_permissions(permissions) # Permission-based filtering with hierarchy # conversation_manage > conversation_unassigned_manage > conversation_participating_manage if permissions.include?('conversation_manage') - conversations + accessible_conversations elsif permissions.include?('conversation_unassigned_manage') filter_unassigned_and_mine elsif permissions.include?('conversation_participating_manage') - conversations.assigned_to(user) + accessible_conversations.assigned_to(user) else Conversation.none end end def filter_unassigned_and_mine - mine = conversations.assigned_to(user) - unassigned = conversations.unassigned + mine = accessible_conversations.assigned_to(user) + unassigned = accessible_conversations.unassigned Conversation.from("(#{mine.to_sql} UNION #{unassigned.to_sql}) as conversations") .where(account_id: account.id) diff --git a/enterprise/app/views/api/v1/accounts/captain/copilot_messages/create.json.jbuilder b/enterprise/app/views/api/v1/accounts/captain/copilot_messages/create.json.jbuilder new file mode 100644 index 000000000..d47ecdec4 --- /dev/null +++ b/enterprise/app/views/api/v1/accounts/captain/copilot_messages/create.json.jbuilder @@ -0,0 +1 @@ +json.partial! 'api/v1/models/captain/copilot_message', formats: [:json], resource: @copilot_message diff --git a/enterprise/app/views/api/v1/accounts/captain/copilot_messages/index.json.jbuilder b/enterprise/app/views/api/v1/accounts/captain/copilot_messages/index.json.jbuilder index ce0d5b175..42a4d2dfe 100644 --- a/enterprise/app/views/api/v1/accounts/captain/copilot_messages/index.json.jbuilder +++ b/enterprise/app/views/api/v1/accounts/captain/copilot_messages/index.json.jbuilder @@ -1,8 +1,5 @@ json.payload do json.array! @copilot_messages do |message| - json.id message.id - json.message message.message - json.message_type message.message_type - json.created_at message.created_at.to_i + json.partial! 'api/v1/models/captain/copilot_message', formats: [:json], resource: message end end diff --git a/enterprise/app/views/api/v1/accounts/captain/copilot_threads/create.json.jbuilder b/enterprise/app/views/api/v1/accounts/captain/copilot_threads/create.json.jbuilder new file mode 100644 index 000000000..f777d89e2 --- /dev/null +++ b/enterprise/app/views/api/v1/accounts/captain/copilot_threads/create.json.jbuilder @@ -0,0 +1 @@ +json.partial! 'api/v1/models/captain/copilot_thread', formats: [:json], resource: @copilot_thread diff --git a/enterprise/app/views/api/v1/accounts/captain/copilot_threads/index.json.jbuilder b/enterprise/app/views/api/v1/accounts/captain/copilot_threads/index.json.jbuilder index c06182ffd..7e1edae02 100644 --- a/enterprise/app/views/api/v1/accounts/captain/copilot_threads/index.json.jbuilder +++ b/enterprise/app/views/api/v1/accounts/captain/copilot_threads/index.json.jbuilder @@ -1,12 +1,5 @@ json.payload do json.array! @copilot_threads do |thread| - json.id thread.id - json.title thread.title - json.uuid thread.uuid - json.created_at thread.created_at.to_i - json.user do - json.id thread.user.id - json.name thread.user.name - end + json.partial! 'api/v1/models/captain/copilot_thread', resource: thread end end diff --git a/enterprise/app/views/api/v1/models/captain/_copilot_message.json.jbuilder b/enterprise/app/views/api/v1/models/captain/_copilot_message.json.jbuilder new file mode 100644 index 000000000..0ef1d9dd0 --- /dev/null +++ b/enterprise/app/views/api/v1/models/captain/_copilot_message.json.jbuilder @@ -0,0 +1,6 @@ +json.id resource.id +json.message resource.message +json.message_type resource.message_type +json.created_at resource.created_at.to_i +json.copilot_thread resource.copilot_thread.push_event_data +json.account_id resource.account_id diff --git a/enterprise/app/views/api/v1/models/captain/_copilot_thread.json.jbuilder b/enterprise/app/views/api/v1/models/captain/_copilot_thread.json.jbuilder new file mode 100644 index 000000000..d014a3dd3 --- /dev/null +++ b/enterprise/app/views/api/v1/models/captain/_copilot_thread.json.jbuilder @@ -0,0 +1,6 @@ +json.id resource.id +json.title resource.title +json.created_at resource.created_at.to_i +json.user resource.user.push_event_data +json.assistant resource.assistant.push_event_data +json.account_id resource.account_id diff --git a/lib/custom_markdown_renderer.rb b/lib/custom_markdown_renderer.rb index 16bb4e024..902fc20a3 100644 --- a/lib/custom_markdown_renderer.rb +++ b/lib/custom_markdown_renderer.rb @@ -6,6 +6,8 @@ class CustomMarkdownRenderer < CommonMarker::HtmlRenderer VIMEO_REGEX = %r{https?://(?:www\.)?vimeo\.com/(\d+)} MP4_REGEX = %r{https?://(?:www\.)?.+\.(mp4)} ARCADE_REGEX = %r{https?://(?:www\.)?app\.arcade\.software/share/([^&/]+)} + WISTIA_REGEX = %r{https?://(?:www\.)?([^/]+)\.wistia\.com/medias/([^&/]+)} + BUNNY_REGEX = %r{https?://iframe\.mediadelivery\.net/play/(\d+)/([^&/?]+)} def text(node) content = node.string_content @@ -50,7 +52,9 @@ class CustomMarkdownRenderer < CommonMarker::HtmlRenderer VIMEO_REGEX => :make_vimeo_embed, MP4_REGEX => :make_video_embed, LOOM_REGEX => :make_loom_embed, - ARCADE_REGEX => :make_arcade_embed + ARCADE_REGEX => :make_arcade_embed, + WISTIA_REGEX => :make_wistia_embed, + BUNNY_REGEX => :make_bunny_embed } embedding_methods.each do |regex, method| @@ -76,67 +80,36 @@ class CustomMarkdownRenderer < CommonMarker::HtmlRenderer def make_youtube_embed(youtube_match) video_id = youtube_match[1] - %( - - - - ) + EmbedRenderer.youtube(video_id) end def make_loom_embed(loom_match) video_id = loom_match[1] - %( - - - - ) + EmbedRenderer.loom(video_id) end def make_vimeo_embed(vimeo_match) video_id = vimeo_match[1] - %( - - - - ) + EmbedRenderer.vimeo(video_id) end def make_video_embed(link_url) - %( - - - Your browser does not support the video tag. - - ) + EmbedRenderer.video(link_url) + end + + def make_wistia_embed(wistia_match) + video_id = wistia_match[2] + EmbedRenderer.wistia(video_id) end def make_arcade_embed(arcade_match) video_id = arcade_match[1] - %( - - - - - ) + EmbedRenderer.arcade(video_id) + end + + def make_bunny_embed(bunny_match) + library_id = bunny_match[1] + video_id = bunny_match[2] + EmbedRenderer.bunny(library_id, video_id) end end diff --git a/lib/embed_renderer.rb b/lib/embed_renderer.rb new file mode 100644 index 000000000..78f620376 --- /dev/null +++ b/lib/embed_renderer.rb @@ -0,0 +1,102 @@ +module EmbedRenderer + def self.youtube(video_id) + %( + + + + ) + end + + def self.loom(video_id) + %( + + + + ) + end + + def self.vimeo(video_id) + %( + + + + ) + end + + def self.video(link_url) + %( + + + Your browser does not support the video tag. + + ) + end + + # Generates an HTML embed for a Wistia video. + # @param wistia_match [MatchData] A match object from the WISTIA_REGEX regex, where wistia_match[2] contains the video ID. + def self.wistia(video_id) + %( + + + + + + + + ) + end + + def self.arcade(video_id) + %( + + + + + ) + end + + def self.bunny(library_id, video_id) + %( + + + + + ) + end +end diff --git a/lib/events/types.rb b/lib/events/types.rb index ff7eaf778..dce6b8b32 100644 --- a/lib/events/types.rb +++ b/lib/events/types.rb @@ -54,4 +54,7 @@ module Events::Types # agent events AGENT_ADDED = 'agent.added' AGENT_REMOVED = 'agent.removed' + + # copilot events + COPILOT_MESSAGE_CREATED = 'copilot.message.created' end diff --git a/lib/global_config_service.rb b/lib/global_config_service.rb index 8de1c50c9..0649c24af 100644 --- a/lib/global_config_service.rb +++ b/lib/global_config_service.rb @@ -1,6 +1,6 @@ class GlobalConfigService def self.load(config_key, default_value) - config = ENV.fetch(config_key) { GlobalConfig.get(config_key)[config_key] } + config = GlobalConfig.get(config_key)[config_key] return config if config.present? # To support migrating existing instance relying on env variables diff --git a/lib/integrations/slack/hook_builder.rb b/lib/integrations/slack/hook_builder.rb index 139ef3a5b..e9513df57 100644 --- a/lib/integrations/slack/hook_builder.rb +++ b/lib/integrations/slack/hook_builder.rb @@ -32,8 +32,8 @@ class Integrations::Slack::HookBuilder def fetch_access_token client = Slack::Web::Client.new slack_access = client.oauth_v2_access( - client_id: ENV.fetch('SLACK_CLIENT_ID', 'TEST_CLIENT_ID'), - client_secret: ENV.fetch('SLACK_CLIENT_SECRET', 'TEST_CLIENT_SECRET'), + client_id: GlobalConfigService.load('SLACK_CLIENT_ID', 'TEST_CLIENT_ID'), + client_secret: GlobalConfigService.load('SLACK_CLIENT_SECRET', 'TEST_CLIENT_SECRET'), code: params[:code], redirect_uri: Integrations::App.slack_integration_url ) diff --git a/lib/linear.rb b/lib/linear.rb index 9a998c34a..8bf967fc3 100644 --- a/lib/linear.rb +++ b/lib/linear.rb @@ -57,7 +57,8 @@ class Linear assigneeId: params[:assignee_id], priority: params[:priority], labelIds: params[:label_ids], - projectId: params[:project_id] + projectId: params[:project_id], + stateId: params[:state_id] }.compact mutation = Linear::Mutations.issue_create(variables) response = post({ query: mutation }) diff --git a/lib/tasks/db_enhancements.rake b/lib/tasks/db_enhancements.rake index d9cc22334..5fddb6cde 100644 --- a/lib/tasks/db_enhancements.rake +++ b/lib/tasks/db_enhancements.rake @@ -18,7 +18,7 @@ db_namespace = namespace :db do ActiveRecord::Base.configurations.configs_for(env_name: Rails.env).each do |db_config| ActiveRecord::Base.establish_connection(db_config.configuration_hash) unless ActiveRecord::Base.connection.table_exists? 'ar_internal_metadata' - db_namespace['load_config'].invoke if ActiveRecord::Base.schema_format == :ruby + db_namespace['load_config'].invoke if ActiveRecord.schema_format == :ruby ActiveRecord::Tasks::DatabaseTasks.load_schema_current(:ruby, ENV.fetch('SCHEMA', nil)) db_namespace['seed'].invoke end diff --git a/lib/tasks/swagger.rake b/lib/tasks/swagger.rake index ef48ce3eb..6bf5f5742 100644 --- a/lib/tasks/swagger.rake +++ b/lib/tasks/swagger.rake @@ -1,22 +1,156 @@ -namespace :swagger do - desc 'build combined swagger.json file from all the fragmented definitions and paths inside swagger folder' - task build: :environment do - require 'json_refs' +require 'json_refs' +require 'fileutils' +require 'pathname' +require 'yaml' +require 'json' - base_path = Rails.root.join('swagger') - Dir.chdir(base_path) do - swagger_index = YAML.safe_load(File.open('index.yml')) +module SwaggerTaskActions + def self.execute_build + swagger_dir = Rails.root.join('swagger') + # Paths relative to swagger_dir for use within Dir.chdir + index_yml_relative_path = 'index.yml' + swagger_json_relative_path = 'swagger.json' + + Dir.chdir(swagger_dir) do + # Operations within this block are relative to swagger_dir + swagger_index_content = File.read(index_yml_relative_path) + swagger_index = YAML.safe_load(swagger_index_content) final_build = JsonRefs.call( swagger_index, resolve_local_ref: false, - resolve_file_ref: true, + resolve_file_ref: true, # Uses CWD (swagger_dir) for resolving file refs logging: true ) - File.write('swagger.json', JSON.pretty_generate(final_build)) + File.write(swagger_json_relative_path, JSON.pretty_generate(final_build)) + + # For user messages, provide the absolute path + absolute_swagger_json_path = swagger_dir.join(swagger_json_relative_path) puts 'Swagger build was successful.' - puts "Generated #{base_path}/swagger.json" + puts "Generated #{absolute_swagger_json_path}" puts 'Go to http://localhost:3000/swagger see the changes.' + + # Trigger dependent task + Rake::Task['swagger:build_tag_groups'].invoke + end + end + + def self.execute_build_tag_groups + base_swagger_path = Rails.root.join('swagger') + tag_groups_output_dir = base_swagger_path.join('tag_groups') + full_spec_path = base_swagger_path.join('swagger.json') + index_yml_path = base_swagger_path.join('index.yml') + + full_spec = JSON.parse(File.read(full_spec_path)) + swagger_index = YAML.safe_load(File.read(index_yml_path)) + tag_groups = swagger_index['x-tagGroups'] + + FileUtils.mkdir_p(tag_groups_output_dir) + + tag_groups.each do |tag_group| + _process_tag_group(tag_group, full_spec, tag_groups_output_dir) + end + + puts 'Tag-specific swagger files generated successfully.' + end + + def self.execute_build_for_docs + Rake::Task['swagger:build'].invoke # Ensure all swagger files are built first + + developer_docs_public_path = Rails.root.join('developer-docs/public') + tag_groups_in_dev_docs_path = developer_docs_public_path.join('swagger/tag_groups') + source_tag_groups_path = Rails.root.join('swagger/tag_groups') + + FileUtils.mkdir_p(tag_groups_in_dev_docs_path) + puts 'Creating symlinks for developer-docs...' + + symlink_files = %w[platform_swagger.json application_swagger.json client_swagger.json other_swagger.json] + symlink_files.each do |file| + _create_symlink(source_tag_groups_path.join(file), tag_groups_in_dev_docs_path.join(file)) + end + + puts 'Symlinks created successfully.' + puts 'You can now run the Mintlify dev server to preview the documentation.' + end + + # Private helper methods + class << self + private + + def _process_tag_group(tag_group, full_spec, output_dir) + group_name = tag_group['name'] + tags_in_current_group = tag_group['tags'] + + tag_spec = JSON.parse(JSON.generate(full_spec)) # Deep clone + + tag_spec['paths'] = _filter_paths_for_tag_group(tag_spec['paths'], tags_in_current_group) + tag_spec['tags'] = _filter_tags_for_tag_group(tag_spec['tags'], tags_in_current_group) + + output_filename = _determine_output_filename(group_name) + File.write(output_dir.join(output_filename), JSON.pretty_generate(tag_spec)) + end + + def _operation_has_matching_tags?(operation, tags_in_group) + return false unless operation.is_a?(Hash) + + operation_tags = operation['tags'] + return false unless operation_tags.is_a?(Array) + + operation_tags.intersect?(tags_in_group) + end + + def _filter_paths_for_tag_group(paths_spec, tags_in_group) + (paths_spec || {}).filter_map do |path, path_item| + next unless path_item.is_a?(Hash) + + operations_with_group_tags = path_item.any? do |_method, operation| + _operation_has_matching_tags?(operation, tags_in_group) + end + [path, path_item] if operations_with_group_tags + end.to_h + end + + def _filter_tags_for_tag_group(tags_spec, tags_in_group) + if tags_spec.is_a?(Array) + tags_spec.select { |tag_definition| tags_in_group.include?(tag_definition['name']) } + else + [] + end + end + + def _determine_output_filename(group_name) + return 'other_swagger.json' if group_name.casecmp('others').zero? + + sanitized_group_name = group_name.downcase.tr(' ', '_').gsub(/[^a-z0-9_]+/, '') + "#{sanitized_group_name}_swagger.json" + end + + def _create_symlink(source_file_path, target_file_path) + FileUtils.rm_f(target_file_path) # Remove existing to avoid errors + + if File.exist?(source_file_path) + relative_source_path = Pathname.new(source_file_path).relative_path_from(target_file_path.dirname) + FileUtils.ln_sf(relative_source_path, target_file_path) + else + puts "Warning: Source file #{source_file_path} not found. Skipping symlink for #{File.basename(target_file_path)}." + end end end end + +namespace :swagger do + desc 'build combined swagger.json file from all the fragmented definitions and paths inside swagger folder' + task build: :environment do + SwaggerTaskActions.execute_build + end + + desc 'build separate swagger files for each tag group' + task build_tag_groups: :environment do + SwaggerTaskActions.execute_build_tag_groups + end + + desc 'build swagger files and create symlinks in developer-docs' + task build_for_docs: :environment do + SwaggerTaskActions.execute_build_for_docs + end +end diff --git a/package.json b/package.json index 88c775f28..90b7708f6 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@chatwoot/chatwoot", - "version": "4.1.0", + "version": "4.2.0", "license": "MIT", "scripts": { "eslint": "eslint app/**/*.{js,vue}", diff --git a/spec/builders/account_builder_spec.rb b/spec/builders/account_builder_spec.rb new file mode 100644 index 000000000..86cb4cf73 --- /dev/null +++ b/spec/builders/account_builder_spec.rb @@ -0,0 +1,57 @@ +# frozen_string_literal: true + +require 'rails_helper' + +RSpec.describe AccountBuilder do + let(:email) { 'user@example.com' } + let(:user_password) { 'Password123!' } + let(:account_name) { 'Test Account' } + let(:user_full_name) { 'Test User' } + let(:validation_service) { instance_double(Account::SignUpEmailValidationService, perform: true) } + let(:account_builder) do + described_class.new( + account_name: account_name, + email: email, + user_full_name: user_full_name, + user_password: user_password, + confirmed: true + ) + end + + # Mock the email validation service + before do + allow(Account::SignUpEmailValidationService).to receive(:new).with(email).and_return(validation_service) + end + + describe '#perform' do + context 'when valid params are passed' do + it 'creates a new account with correct name' do + _user, account = account_builder.perform + expect(account).to be_an(Account) + expect(account.name).to eq(account_name) + end + + it 'creates a new confirmed user with correct details' do + user, _account = account_builder.perform + expect(user).to be_a(User) + expect(user.email).to eq(email) + expect(user.name).to eq(user_full_name) + expect(user.confirmed?).to be(true) + end + + it 'links user to account as administrator' do + user, account = account_builder.perform + expect(user.account_users.first.role).to eq('administrator') + expect(user.accounts.first).to eq(account) + end + + it 'increments the counts of models' do + expect do + account_builder.perform + end.to change(Account, :count).by(1) + .and change(User, :count).by(1) + .and change(AccountUser, :count).by(1) + end + end + end +end diff --git a/spec/builders/messages/instagram/message_builder_spec.rb b/spec/builders/messages/instagram/message_builder_spec.rb index 0dbe87afa..8e863823a 100644 --- a/spec/builders/messages/instagram/message_builder_spec.rb +++ b/spec/builders/messages/instagram/message_builder_spec.rb @@ -79,6 +79,18 @@ describe Messages::Instagram::MessageBuilder do expect(instagram_inbox.messages.count).to be 1 end + it 'discards duplicate messages from webhook events with the same message_id' do + messaging = dm_params[:entry][0]['messaging'][0] + described_class.new(messaging, instagram_inbox).perform + + initial_message_count = instagram_inbox.messages.count + expect(initial_message_count).to be 1 + + described_class.new(messaging, instagram_inbox).perform + + expect(instagram_inbox.messages.count).to eq initial_message_count + end + it 'creates message for shared reel' do messaging = shared_reel_params[:entry][0]['messaging'][0] described_class.new(messaging, instagram_inbox).perform @@ -151,11 +163,15 @@ describe Messages::Instagram::MessageBuilder do end it 'does not create message for unsupported file type' do + conversation + + # try to create a message with unsupported file type story_mention_params[:entry][0][:messaging][0]['message']['attachments'][0]['type'] = 'unsupported_type' messaging = story_mention_params[:entry][0][:messaging][0] described_class.new(messaging, instagram_inbox, outgoing_echo: false).perform + # Conversation should exist but no new message should be created expect(instagram_inbox.conversations.count).to be 1 expect(instagram_inbox.messages.count).to be 0 end diff --git a/spec/builders/messages/instagram/messenger/message_builder_spec.rb b/spec/builders/messages/instagram/messenger/message_builder_spec.rb index 78c97b071..03194673a 100644 --- a/spec/builders/messages/instagram/messenger/message_builder_spec.rb +++ b/spec/builders/messages/instagram/messenger/message_builder_spec.rb @@ -189,19 +189,22 @@ describe Messages::Instagram::Messenger::MessageBuilder do profile_pic: 'https://chatwoot-assets.local/sample.png' }.with_indifferent_access ) + + conversation + + # create a message with unsupported file type story_mention_params[:entry][0][:messaging][0]['message']['attachments'][0]['type'] = 'unsupported_type' messaging = story_mention_params[:entry][0][:messaging][0] - contact_inbox + described_class.new(messaging, instagram_messenger_inbox, outgoing_echo: false).perform instagram_messenger_inbox.reload - # we would have contact created but message and attachments won't be created + # Conversation should exist but no new message should be created expect(instagram_messenger_inbox.conversations.count).to be 1 expect(instagram_messenger_inbox.messages.count).to be 0 contact = instagram_messenger_channel.inbox.contacts.first - expect(contact.name).to eq('Jane Dae') end end diff --git a/spec/builders/v2/reports/conversations/report_builder_spec.rb b/spec/builders/v2/reports/conversations/report_builder_spec.rb index d3cde98e6..db7a0ac45 100644 --- a/spec/builders/v2/reports/conversations/report_builder_spec.rb +++ b/spec/builders/v2/reports/conversations/report_builder_spec.rb @@ -33,12 +33,12 @@ describe V2::Reports::Conversations::ReportBuilder do end describe '#timeseries' do - include_examples 'valid metric handler', 'avg_first_response_time', :timeseries, V2::Reports::Timeseries::AverageReportBuilder - include_examples 'valid metric handler', 'conversations_count', :timeseries, V2::Reports::Timeseries::CountReportBuilder + it_behaves_like 'valid metric handler', 'avg_first_response_time', :timeseries, V2::Reports::Timeseries::AverageReportBuilder + it_behaves_like 'valid metric handler', 'conversations_count', :timeseries, V2::Reports::Timeseries::CountReportBuilder end describe '#aggregate_value' do - include_examples 'valid metric handler', 'avg_first_response_time', :aggregate_value, V2::Reports::Timeseries::AverageReportBuilder - include_examples 'valid metric handler', 'conversations_count', :aggregate_value, V2::Reports::Timeseries::CountReportBuilder + it_behaves_like 'valid metric handler', 'avg_first_response_time', :aggregate_value, V2::Reports::Timeseries::AverageReportBuilder + it_behaves_like 'valid metric handler', 'conversations_count', :aggregate_value, V2::Reports::Timeseries::CountReportBuilder end end diff --git a/spec/controllers/api/v1/accounts/integrations/linear_controller_spec.rb b/spec/controllers/api/v1/accounts/integrations/linear_controller_spec.rb index b1341e65e..0f27e2bd2 100644 --- a/spec/controllers/api/v1/accounts/integrations/linear_controller_spec.rb +++ b/spec/controllers/api/v1/accounts/integrations/linear_controller_spec.rb @@ -100,6 +100,7 @@ RSpec.describe 'Linear Integration API', type: :request do description: 'This is a sample issue.', assignee_id: 'user1', priority: 'high', + state_id: 'state1', label_ids: ['label1'] } end diff --git a/spec/controllers/api/v1/accounts/search_controller_spec.rb b/spec/controllers/api/v1/accounts/search_controller_spec.rb index b5644cebf..ea59bec9c 100644 --- a/spec/controllers/api/v1/accounts/search_controller_spec.rb +++ b/spec/controllers/api/v1/accounts/search_controller_spec.rb @@ -11,6 +11,11 @@ RSpec.describe 'Search', type: :request do create(:message, conversation: conversation, account: account, content: 'test2') create(:contact_inbox, contact_id: contact.id, inbox_id: conversation.inbox.id) create(:inbox_member, user: agent, inbox: conversation.inbox) + + # Create articles for testing + portal = create(:portal, account: account) + create(:article, title: 'Test Article Guide', content: 'This is a test article content', + account: account, portal: portal, author: agent, status: 'published') end describe 'GET /api/v1/accounts/{account.id}/search' do @@ -33,10 +38,11 @@ RSpec.describe 'Search', type: :request do response_data = JSON.parse(response.body, symbolize_names: true) expect(response_data[:payload][:messages].first[:content]).to eq 'test2' - expect(response_data[:payload].keys).to contain_exactly(:contacts, :conversations, :messages) + expect(response_data[:payload].keys).to contain_exactly(:contacts, :conversations, :messages, :articles) expect(response_data[:payload][:messages].length).to eq 2 expect(response_data[:payload][:conversations].length).to eq 1 expect(response_data[:payload][:contacts].length).to eq 1 + expect(response_data[:payload][:articles].length).to eq 1 end end end @@ -115,4 +121,60 @@ RSpec.describe 'Search', type: :request do end end end + + describe 'GET /api/v1/accounts/{account.id}/search/articles' do + context 'when it is an unauthenticated user' do + it 'returns unauthorized' do + get "/api/v1/accounts/#{account.id}/search/articles", params: { q: 'test' } + + expect(response).to have_http_status(:unauthorized) + end + end + + context 'when it is an authenticated user' do + it 'returns all articles containing the search query' do + get "/api/v1/accounts/#{account.id}/search/articles", + headers: agent.create_new_auth_token, + params: { q: 'test' }, + as: :json + + expect(response).to have_http_status(:success) + response_data = JSON.parse(response.body, symbolize_names: true) + + expect(response_data[:payload].keys).to contain_exactly(:articles) + expect(response_data[:payload][:articles].length).to eq 1 + expect(response_data[:payload][:articles].first[:title]).to eq 'Test Article Guide' + end + + it 'returns empty results when no articles match the search query' do + get "/api/v1/accounts/#{account.id}/search/articles", + headers: agent.create_new_auth_token, + params: { q: 'nonexistent' }, + as: :json + + expect(response).to have_http_status(:success) + response_data = JSON.parse(response.body, symbolize_names: true) + + expect(response_data[:payload].keys).to contain_exactly(:articles) + expect(response_data[:payload][:articles].length).to eq 0 + end + + it 'supports pagination' do + portal = create(:portal, account: account) + 16.times do |i| + create(:article, title: "Test Article #{i}", account: account, portal: portal, author: agent, status: 'published') + end + + get "/api/v1/accounts/#{account.id}/search/articles", + headers: agent.create_new_auth_token, + params: { q: 'test', page: 1 }, + as: :json + + expect(response).to have_http_status(:success) + response_data = JSON.parse(response.body, symbolize_names: true) + + expect(response_data[:payload][:articles].length).to eq 15 # Default per_page is 15 + end + end + end end diff --git a/spec/controllers/devise/omniauth_callbacks_controller_spec.rb b/spec/controllers/devise/omniauth_callbacks_controller_spec.rb index 3513b9c34..1a775f88f 100644 --- a/spec/controllers/devise/omniauth_callbacks_controller_spec.rb +++ b/spec/controllers/devise/omniauth_callbacks_controller_spec.rb @@ -3,6 +3,7 @@ require 'rails_helper' RSpec.describe 'DeviseOverrides::OmniauthCallbacksController', type: :request do let(:account_builder) { double } let(:user_double) { object_double(:user) } + let(:email_validation_service) { instance_double(Account::SignUpEmailValidationService) } def set_omniauth_config(for_email = 'test@example.com') OmniAuth.config.test_mode = true @@ -17,13 +18,22 @@ RSpec.describe 'DeviseOverrides::OmniauthCallbacksController', type: :request do ) end + before do + allow(Account::SignUpEmailValidationService).to receive(:new).and_return(email_validation_service) + end + describe '#omniauth_sucess' do + before do + GlobalConfig.clear_cache + end + it 'allows signup' do - with_modified_env ENABLE_ACCOUNT_SIGNUP: 'true' do + with_modified_env ENABLE_ACCOUNT_SIGNUP: 'true', FRONTEND_URL: 'http://www.example.com' do set_omniauth_config('test_not_preset@example.com') allow(AccountBuilder).to receive(:new).and_return(account_builder) allow(account_builder).to receive(:perform).and_return(user_double) allow(Avatar::AvatarFromUrlJob).to receive(:perform_later).and_return(true) + allow(email_validation_service).to receive(:perform).and_return(true) get '/omniauth/google_oauth2/callback' @@ -43,8 +53,10 @@ RSpec.describe 'DeviseOverrides::OmniauthCallbacksController', type: :request do end it 'blocks personal accounts signup' do - with_modified_env ENABLE_ACCOUNT_SIGNUP: 'true' do + with_modified_env ENABLE_ACCOUNT_SIGNUP: 'true', FRONTEND_URL: 'http://www.example.com' do set_omniauth_config('personal@gmail.com') + allow(email_validation_service).to receive(:perform).and_raise(CustomExceptions::Account::InvalidEmail.new({ valid: false, disposable: nil })) + get '/omniauth/google_oauth2/callback' # expect a 302 redirect to auth/google_oauth2/callback @@ -57,10 +69,13 @@ RSpec.describe 'DeviseOverrides::OmniauthCallbacksController', type: :request do end it 'blocks personal accounts signup with different Gmail case variations' do - with_modified_env ENABLE_ACCOUNT_SIGNUP: 'true' do + with_modified_env ENABLE_ACCOUNT_SIGNUP: 'true', FRONTEND_URL: 'http://www.example.com' do # Test different case variations of Gmail ['personal@Gmail.com', 'personal@GMAIL.com', 'personal@Gmail.COM'].each do |email| set_omniauth_config(email) + allow(email_validation_service).to receive(:perform).and_raise(CustomExceptions::Account::InvalidEmail.new({ valid: false, + disposable: nil })) + get '/omniauth/google_oauth2/callback' # expect a 302 redirect to auth/google_oauth2/callback @@ -76,8 +91,10 @@ RSpec.describe 'DeviseOverrides::OmniauthCallbacksController', type: :request do # This test does not affect line coverage, but it is important to ensure that the logic # does not allow any signup if the ENV explicitly disables it it 'blocks signup if ENV disabled' do - with_modified_env ENABLE_ACCOUNT_SIGNUP: 'false' do + with_modified_env ENABLE_ACCOUNT_SIGNUP: 'false', FRONTEND_URL: 'http://www.example.com' do set_omniauth_config('does-not-exist-for-sure@example.com') + allow(email_validation_service).to receive(:perform).and_return(true) + get '/omniauth/google_oauth2/callback' # expect a 302 redirect to auth/google_oauth2/callback @@ -90,38 +107,42 @@ RSpec.describe 'DeviseOverrides::OmniauthCallbacksController', type: :request do end it 'allows login' do - create(:user, email: 'test@example.com') - set_omniauth_config('test@example.com') + with_modified_env FRONTEND_URL: 'http://www.example.com' do + create(:user, email: 'test@example.com') + set_omniauth_config('test@example.com') - get '/omniauth/google_oauth2/callback' - # expect a 302 redirect to auth/google_oauth2/callback - expect(response).to redirect_to('http://www.example.com/auth/google_oauth2/callback') + get '/omniauth/google_oauth2/callback' + # expect a 302 redirect to auth/google_oauth2/callback + expect(response).to redirect_to('http://www.example.com/auth/google_oauth2/callback') - follow_redirect! - expect(response).to redirect_to(%r{/app/login\?email=.+&sso_auth_token=.+$}) + follow_redirect! + expect(response).to redirect_to(%r{/app/login\?email=.+&sso_auth_token=.+$}) - # expect app/login page to respond with 200 and render - follow_redirect! - expect(response).to have_http_status(:ok) + # expect app/login page to respond with 200 and render + follow_redirect! + expect(response).to have_http_status(:ok) + end end # from a line coverage point of view this may seem redundant # but to ensure that the logic allows for existing users even if they have a gmail account # we need to test this explicitly it 'allows personal account login' do - create(:user, email: 'personal-existing@gmail.com') - set_omniauth_config('personal-existing@gmail.com') + with_modified_env FRONTEND_URL: 'http://www.example.com' do + create(:user, email: 'personal-existing@gmail.com') + set_omniauth_config('personal-existing@gmail.com') - get '/omniauth/google_oauth2/callback' - # expect a 302 redirect to auth/google_oauth2/callback - expect(response).to redirect_to('http://www.example.com/auth/google_oauth2/callback') + get '/omniauth/google_oauth2/callback' + # expect a 302 redirect to auth/google_oauth2/callback + expect(response).to redirect_to('http://www.example.com/auth/google_oauth2/callback') - follow_redirect! - expect(response).to redirect_to(%r{/app/login\?email=.+&sso_auth_token=.+$}) + follow_redirect! + expect(response).to redirect_to(%r{/app/login\?email=.+&sso_auth_token=.+$}) - # expect app/login page to respond with 200 and render - follow_redirect! - expect(response).to have_http_status(:ok) + # expect app/login page to respond with 200 and render + follow_redirect! + expect(response).to have_http_status(:ok) + end end end end diff --git a/spec/controllers/platform/api/v1/users_controller_spec.rb b/spec/controllers/platform/api/v1/users_controller_spec.rb index 895f30b29..c73a03654 100644 --- a/spec/controllers/platform/api/v1/users_controller_spec.rb +++ b/spec/controllers/platform/api/v1/users_controller_spec.rb @@ -76,6 +76,54 @@ RSpec.describe 'Platform Users API', type: :request do end end + describe 'POST /platform/api/v1/users/{user_id}/token' do + context 'when it is an unauthenticated platform app' do + it 'returns unauthorized' do + post "/platform/api/v1/users/#{user.id}/token" + expect(response).to have_http_status(:unauthorized) + end + end + + context 'when it is an invalid platform app token' do + it 'returns unauthorized' do + post "/platform/api/v1/users/#{user.id}/token", headers: { api_access_token: 'invalid' }, as: :json + expect(response).to have_http_status(:unauthorized) + end + end + + context 'when it is an authenticated platform app' do + let(:platform_app) { create(:platform_app) } + + it 'returns unauthorized when its not a permissible object' do + post "/platform/api/v1/users/#{user.id}/token", headers: { api_access_token: platform_app.access_token.token }, as: :json + expect(response).to have_http_status(:unauthorized) + end + + it 'returns access token for the user with expiry and user info' do + create(:platform_app_permissible, platform_app: platform_app, permissible: user) + + post "/platform/api/v1/users/#{user.id}/token", + headers: { api_access_token: platform_app.access_token.token }, as: :json + + expect(response).to have_http_status(:success) + data = response.parsed_body + + # Check access token and expiry + expect(data['access_token']).to eq(user.access_token.token) + expect(data['expiry']).to be_nil + + # Check user info + expect(data['user']).to include( + 'id' => user.id, + 'name' => user.name, + 'display_name' => user.display_name, + 'email' => user.email, + 'pubsub_token' => user.pubsub_token + ) + end + end + end + describe 'POST /platform/api/v1/users/' do context 'when it is an unauthenticated platform app' do it 'returns unauthorized' do diff --git a/spec/enterprise/controllers/api/v1/accounts/captain/copilot_messages_controller_spec.rb b/spec/enterprise/controllers/api/v1/accounts/captain/copilot_messages_controller_spec.rb index 0ccca90c5..511f4ea73 100644 --- a/spec/enterprise/controllers/api/v1/accounts/captain/copilot_messages_controller_spec.rb +++ b/spec/enterprise/controllers/api/v1/accounts/captain/copilot_messages_controller_spec.rb @@ -4,12 +4,12 @@ RSpec.describe 'Api::V1::Accounts::Captain::CopilotMessagesController', type: :r let(:account) { create(:account) } let(:user) { create(:user, account: account, role: :administrator) } let(:copilot_thread) { create(:captain_copilot_thread, account: account, user: user) } - let!(:copilot_message) { create(:captain_copilot_message, copilot_thread: copilot_thread, user: user, account: account) } + let!(:copilot_message) { create(:captain_copilot_message, copilot_thread: copilot_thread, account: account) } - describe 'GET /api/v1/accounts/{account.id}/captain/copilot_threads/{thread.uuid}/copilot_messages' do + describe 'GET /api/v1/accounts/{account.id}/captain/copilot_threads/{thread.id}/copilot_messages' do context 'when it is an authenticated user' do it 'returns all messages' do - get "/api/v1/accounts/#{account.id}/captain/copilot_threads/#{copilot_thread.uuid}/copilot_messages", + get "/api/v1/accounts/#{account.id}/captain/copilot_threads/#{copilot_thread.id}/copilot_messages", headers: user.create_new_auth_token, as: :json @@ -20,9 +20,9 @@ RSpec.describe 'Api::V1::Accounts::Captain::CopilotMessagesController', type: :r end end - context 'when thread uuid is invalid' do + context 'when thread id is invalid' do it 'returns not found error' do - get "/api/v1/accounts/#{account.id}/captain/copilot_threads/invalid-uuid/copilot_messages", + get "/api/v1/accounts/#{account.id}/captain/copilot_threads/999999999/copilot_messages", headers: user.create_new_auth_token, as: :json @@ -30,4 +30,49 @@ RSpec.describe 'Api::V1::Accounts::Captain::CopilotMessagesController', type: :r end end end + + describe 'POST /api/v1/accounts/{account.id}/captain/copilot_threads/{thread.id}/copilot_messages' do + context 'when it is an authenticated user' do + it 'creates a new message' do + message_content = { 'content' => 'This is a test message' } + + expect do + post "/api/v1/accounts/#{account.id}/captain/copilot_threads/#{copilot_thread.id}/copilot_messages", + params: { message: message_content }, + headers: user.create_new_auth_token, + as: :json + end.to change(CopilotMessage, :count).by(1) + + expect(response).to have_http_status(:success) + expect(CopilotMessage.last.message).to eq({ 'content' => message_content }) + expect(CopilotMessage.last.message_type).to eq('user') + expect(CopilotMessage.last.copilot_thread_id).to eq(copilot_thread.id) + end + end + + context 'when thread does not exist' do + it 'returns not found error' do + post "/api/v1/accounts/#{account.id}/captain/copilot_threads/999999999/copilot_messages", + params: { message: { text: 'Test message' } }, + headers: user.create_new_auth_token, + as: :json + + expect(response).to have_http_status(:not_found) + end + end + + context 'when thread belongs to another user' do + let(:another_user) { create(:user, account: account) } + let(:another_thread) { create(:captain_copilot_thread, account: account, user: another_user) } + + it 'returns not found error' do + post "/api/v1/accounts/#{account.id}/captain/copilot_threads/#{another_thread.id}/copilot_messages", + params: { message: { text: 'Test message' } }, + headers: user.create_new_auth_token, + as: :json + + expect(response).to have_http_status(:not_found) + end + end + end end diff --git a/spec/enterprise/controllers/api/v1/accounts/captain/copilot_threads_controller_spec.rb b/spec/enterprise/controllers/api/v1/accounts/captain/copilot_threads_controller_spec.rb index 8533a2d1d..b8fc628d1 100644 --- a/spec/enterprise/controllers/api/v1/accounts/captain/copilot_threads_controller_spec.rb +++ b/spec/enterprise/controllers/api/v1/accounts/captain/copilot_threads_controller_spec.rb @@ -4,6 +4,7 @@ RSpec.describe 'Api::V1::Accounts::Captain::CopilotThreads', type: :request do let(:account) { create(:account) } let(:admin) { create(:user, account: account, role: :administrator) } let(:agent) { create(:user, account: account, role: :agent) } + let(:conversation) { create(:conversation, account: account) } def json_response JSON.parse(response.body, symbolize_names: true) @@ -18,7 +19,7 @@ RSpec.describe 'Api::V1::Accounts::Captain::CopilotThreads', type: :request do end end - context 'when it is an agent' do + context 'when it is an authenticated user' do it 'fetches copilot threads for the current user' do # Create threads for the current agent create_list(:captain_copilot_thread, 3, account: account, user: agent) @@ -47,4 +48,65 @@ RSpec.describe 'Api::V1::Accounts::Captain::CopilotThreads', type: :request do end end end + + describe 'POST /api/v1/accounts/{account.id}/captain/copilot_threads' do + let(:assistant) { create(:captain_assistant, account: account) } + let(:valid_params) { { message: 'Hello, how can you help me?', assistant_id: assistant.id, conversation_id: conversation.display_id } } + + context 'when it is an un-authenticated user' do + it 'returns unauthorized' do + post "/api/v1/accounts/#{account.id}/captain/copilot_threads", + params: valid_params, + as: :json + + expect(response).to have_http_status(:unauthorized) + end + end + + context 'when it is an authenticated user' do + context 'with invalid params' do + it 'returns error when message is blank' do + post "/api/v1/accounts/#{account.id}/captain/copilot_threads", + params: { message: '', assistant_id: assistant.id }, + headers: agent.create_new_auth_token, + as: :json + + expect(response).to have_http_status(:unprocessable_entity) + expect(json_response[:error]).to eq('Message is required') + end + + it 'returns error when assistant_id is invalid' do + post "/api/v1/accounts/#{account.id}/captain/copilot_threads", + params: { message: 'Hello', assistant_id: 0 }, + headers: agent.create_new_auth_token, + as: :json + + expect(response).to have_http_status(:not_found) + end + end + + context 'with valid params' do + it 'creates a new copilot thread with initial message' do + expect do + post "/api/v1/accounts/#{account.id}/captain/copilot_threads", + params: valid_params, + headers: agent.create_new_auth_token, + as: :json + end.to change(CopilotThread, :count).by(1) + .and change(CopilotMessage, :count).by(1) + + expect(response).to have_http_status(:success) + + thread = CopilotThread.last + expect(thread.title).to eq(valid_params[:message]) + expect(thread.user_id).to eq(agent.id) + expect(thread.assistant_id).to eq(assistant.id) + + message = thread.copilot_messages.last + expect(message.message_type).to eq('user') + expect(message.message).to eq({ 'content' => valid_params[:message] }) + end + end + end + end end diff --git a/spec/enterprise/jobs/captain/copilot/response_job_spec.rb b/spec/enterprise/jobs/captain/copilot/response_job_spec.rb new file mode 100644 index 000000000..c8aacda5e --- /dev/null +++ b/spec/enterprise/jobs/captain/copilot/response_job_spec.rb @@ -0,0 +1,41 @@ +require 'rails_helper' + +RSpec.describe Captain::Copilot::ResponseJob, type: :job do + let(:account) { create(:account) } + let(:user) { create(:user, account: account) } + let(:assistant) { create(:captain_assistant, account: account) } + let(:copilot_thread) { create(:captain_copilot_thread, account: account, user: user, assistant: assistant) } + let(:conversation_id) { 123 } + let(:message) { { 'content' => 'Test message' } } + + describe '#perform' do + let(:chat_service) { instance_double(Captain::Copilot::ChatService) } + + before do + allow(Captain::Copilot::ChatService).to receive(:new).with( + assistant, + user_id: user.id, + copilot_thread_id: copilot_thread.id, + conversation_id: conversation_id + ).and_return(chat_service) + allow(chat_service).to receive(:generate_response).with(message) + end + + it 'initializes ChatService with correct parameters and calls generate_response' do + expect(Captain::Copilot::ChatService).to receive(:new).with( + assistant, + user_id: user.id, + copilot_thread_id: copilot_thread.id, + conversation_id: conversation_id + ) + expect(chat_service).to receive(:generate_response).with(message) + described_class.perform_now( + assistant: assistant, + conversation_id: conversation_id, + user_id: user.id, + copilot_thread_id: copilot_thread.id, + message: message + ) + end + end +end diff --git a/spec/enterprise/listeners/action_cable_listener_spec.rb b/spec/enterprise/listeners/action_cable_listener_spec.rb new file mode 100644 index 000000000..9cb307809 --- /dev/null +++ b/spec/enterprise/listeners/action_cable_listener_spec.rb @@ -0,0 +1,24 @@ +require 'rails_helper' + +describe ActionCableListener do + describe '#copilot_message_created' do + let(:event_name) { :copilot_message_created } + let(:account) { create(:account) } + let(:user) { create(:user, account: account) } + let(:assistant) { create(:captain_assistant, account: account) } + let(:copilot_thread) { create(:captain_copilot_thread, account: account, user: user, assistant: assistant) } + let(:copilot_message) { create(:captain_copilot_message, copilot_thread: copilot_thread) } + let(:event) { Events::Base.new(event_name, Time.zone.now, copilot_message: copilot_message) } + let(:listener) { described_class.instance } + + it 'broadcasts message to the user' do + expect(ActionCableBroadcastJob).to receive(:perform_later).with( + [user.pubsub_token], + 'copilot.message.created', + copilot_message.push_event_data.merge(account_id: account.id) + ) + + listener.copilot_message_created(event) + end + end +end diff --git a/spec/enterprise/listeners/captain_listener_spec.rb b/spec/enterprise/listeners/captain_listener_spec.rb new file mode 100644 index 000000000..1363ea033 --- /dev/null +++ b/spec/enterprise/listeners/captain_listener_spec.rb @@ -0,0 +1,57 @@ +require 'rails_helper' + +describe CaptainListener do + let(:listener) { described_class.instance } + let(:account) { create(:account) } + let(:inbox) { create(:inbox, account: account) } + let(:user) { create(:user, account: account) } + let(:assistant) { create(:captain_assistant, account: account, config: { feature_memory: true, feature_faq: true }) } + + describe '#conversation_resolved' do + let(:agent) { create(:user, account: account) } + let(:conversation) { create(:conversation, account: account, inbox: inbox, assignee: agent) } + + let(:event_name) { :conversation_resolved } + let(:event) { Events::Base.new(event_name, Time.zone.now, conversation: conversation) } + + before do + create(:captain_inbox, captain_assistant: assistant, inbox: inbox) + end + + context 'when feature_memory is enabled' do + before do + assistant.config['feature_memory'] = true + assistant.config['feature_faq'] = false + assistant.save! + end + + it 'generates and updates notes' do + expect(Captain::Llm::ContactNotesService) + .to receive(:new) + .with(assistant, conversation) + .and_return(instance_double(Captain::Llm::ContactNotesService, generate_and_update_notes: nil)) + expect(Captain::Llm::ConversationFaqService).not_to receive(:new) + + listener.conversation_resolved(event) + end + end + + context 'when feature_faq is enabled' do + before do + assistant.config['feature_faq'] = true + assistant.config['feature_memory'] = false + assistant.save! + end + + it 'generates and deduplicates FAQs' do + expect(Captain::Llm::ConversationFaqService) + .to receive(:new) + .with(assistant, conversation) + .and_return(instance_double(Captain::Llm::ConversationFaqService, generate_and_deduplicate: false)) + expect(Captain::Llm::ContactNotesService).not_to receive(:new) + + listener.conversation_resolved(event) + end + end + end +end diff --git a/spec/enterprise/models/copilot_message_spec.rb b/spec/enterprise/models/copilot_message_spec.rb new file mode 100644 index 000000000..6c0e496f2 --- /dev/null +++ b/spec/enterprise/models/copilot_message_spec.rb @@ -0,0 +1,63 @@ +require 'rails_helper' + +RSpec.describe CopilotMessage, type: :model do + let(:account) { create(:account) } + let(:user) { create(:user, account: account) } + let(:assistant) { create(:captain_assistant, account: account) } + let(:copilot_thread) { create(:captain_copilot_thread, account: account, user: user, assistant: assistant) } + + describe 'validations' do + it { is_expected.to validate_presence_of(:message_type) } + it { is_expected.to validate_presence_of(:message) } + end + + describe 'callbacks' do + let(:account) { create(:account) } + let(:user) { create(:user, account: account) } + let(:assistant) { create(:captain_assistant, account: account) } + let(:copilot_thread) { create(:captain_copilot_thread, account: account, user: user, assistant: assistant) } + + describe '#ensure_account' do + it 'sets the account from the copilot thread before validation' do + message = build(:captain_copilot_message, copilot_thread: copilot_thread, account: nil) + message.valid? + expect(message.account).to eq(copilot_thread.account) + end + end + + describe '#broadcast_message' do + it 'dispatches COPILOT_MESSAGE_CREATED event after create' do + message = build(:captain_copilot_message, copilot_thread: copilot_thread) + + expect(Rails.configuration.dispatcher).to receive(:dispatch) + .with('copilot.message.created', anything, copilot_message: message) + + message.save! + end + end + end + + describe '#push_event_data' do + let(:account) { create(:account) } + let(:user) { create(:user, account: account) } + let(:assistant) { create(:captain_assistant, account: account) } + let(:copilot_thread) { create(:captain_copilot_thread, account: account, user: user, assistant: assistant) } + let(:message_content) { { 'content' => 'Test message' } } + let(:copilot_message) do + create(:captain_copilot_message, + copilot_thread: copilot_thread, + message_type: 'user', + message: message_content) + end + + it 'returns the correct event data' do + event_data = copilot_message.push_event_data + + expect(event_data[:id]).to eq(copilot_message.id) + expect(event_data[:message]).to eq(message_content) + expect(event_data[:message_type]).to eq('user') + expect(event_data[:created_at]).to eq(copilot_message.created_at.to_i) + expect(event_data[:copilot_thread]).to eq(copilot_thread.push_event_data) + end + end +end diff --git a/spec/enterprise/models/copilot_thread_spec.rb b/spec/enterprise/models/copilot_thread_spec.rb new file mode 100644 index 000000000..a6a5f4a44 --- /dev/null +++ b/spec/enterprise/models/copilot_thread_spec.rb @@ -0,0 +1,62 @@ +require 'rails_helper' + +RSpec.describe CopilotThread, type: :model do + describe 'associations' do + it { is_expected.to belong_to(:user) } + it { is_expected.to belong_to(:account) } + it { is_expected.to belong_to(:assistant).class_name('Captain::Assistant') } + it { is_expected.to have_many(:copilot_messages).dependent(:destroy_async) } + end + + describe 'validations' do + it { is_expected.to validate_presence_of(:title) } + end + + describe '#push_event_data' do + let(:account) { create(:account) } + let(:user) { create(:user, account: account) } + let(:assistant) { create(:captain_assistant, account: account) } + let(:copilot_thread) { create(:captain_copilot_thread, account: account, user: user, assistant: assistant, title: 'Test Thread') } + + it 'returns the correct event data' do + event_data = copilot_thread.push_event_data + + expect(event_data[:id]).to eq(copilot_thread.id) + expect(event_data[:title]).to eq('Test Thread') + expect(event_data[:created_at]).to eq(copilot_thread.created_at.to_i) + expect(event_data[:user]).to eq(user.push_event_data) + expect(event_data[:account_id]).to eq(account.id) + end + end + + describe '#previous_history' do + let(:account) { create(:account) } + let(:user) { create(:user, account: account) } + let(:assistant) { create(:captain_assistant, account: account) } + let(:copilot_thread) { create(:captain_copilot_thread, account: account, user: user, assistant: assistant) } + + context 'when there are messages in the thread' do + before do + create(:captain_copilot_message, copilot_thread: copilot_thread, message_type: 'user', message: { 'content' => 'User message' }) + create(:captain_copilot_message, copilot_thread: copilot_thread, message_type: 'assistant_thinking', message: { 'content' => 'Thinking...' }) + create(:captain_copilot_message, copilot_thread: copilot_thread, message_type: 'assistant', message: { 'content' => 'Assistant message' }) + end + + it 'returns only user and assistant messages in chronological order' do + history = copilot_thread.previous_history + + expect(history.length).to eq(2) + expect(history[0][:role]).to eq('user') + expect(history[0][:content]).to eq('User message') + expect(history[1][:role]).to eq('assistant') + expect(history[1][:content]).to eq('Assistant message') + end + end + + context 'when there are no messages in the thread' do + it 'returns an empty array' do + expect(copilot_thread.previous_history).to eq([]) + end + end + end +end diff --git a/spec/enterprise/services/captain/copilot/chat_service_spec.rb b/spec/enterprise/services/captain/copilot/chat_service_spec.rb index 5daabd5bf..a4274ec4e 100644 --- a/spec/enterprise/services/captain/copilot/chat_service_spec.rb +++ b/spec/enterprise/services/captain/copilot/chat_service_spec.rb @@ -2,87 +2,245 @@ require 'rails_helper' RSpec.describe Captain::Copilot::ChatService do let(:account) { create(:account, custom_attributes: { plan_name: 'startups' }) } - let(:captain_inbox_association) { create(:captain_inbox, captain_assistant: assistant, inbox: inbox) } - let(:mock_captain_agent) { instance_double(Captain::Agent) } - let(:mock_captain_tool) { instance_double(Captain::Tool) } - let(:mock_openai_client) { instance_double(OpenAI::Client) } + let(:user) { create(:user, account: account) } let(:inbox) { create(:inbox, account: account) } let(:assistant) { create(:captain_assistant, account: account) } + let(:contact) { create(:contact, account: account) } + let(:conversation) { create(:conversation, account: account, inbox: inbox, contact: contact) } + let(:mock_openai_client) { instance_double(OpenAI::Client) } + let(:copilot_thread) { create(:captain_copilot_thread, account: account, user: user) } + let!(:copilot_message) do + create( + :captain_copilot_message, account: account, copilot_thread: copilot_thread + ) + end + let(:previous_history) { [{ role: copilot_message.message_type, content: copilot_message.message['content'] }] } + + let(:config) do + { user_id: user.id, copilot_thread_id: copilot_thread.id, conversation_id: conversation.display_id } + end before do create(:installation_config, name: 'CAPTAIN_OPEN_AI_API_KEY', value: 'test-key') + allow(OpenAI::Client).to receive(:new).and_return(mock_openai_client) + allow(mock_openai_client).to receive(:chat).and_return({ + choices: [{ message: { content: '{ "content": "Hey" }' } }] + }.with_indifferent_access) end describe '#initialize' do - it 'sets default language to english when not specified' do - service = described_class.new(assistant, { previous_messages: [], conversation_history: '' }) - expect(service.instance_variable_get(:@language)).to eq('english') + it 'sets up the service with correct instance variables' do + service = described_class.new(assistant, config) + + expect(service.assistant).to eq(assistant) + expect(service.account).to eq(account) + expect(service.user).to eq(user) + expect(service.copilot_thread).to eq(copilot_thread) + expect(service.previous_history).to eq(previous_history) end - it 'uses the specified language when provided' do - service = described_class.new(assistant, { - previous_messages: [], - conversation_history: '', - language: 'spanish' - }) - expect(service.instance_variable_get(:@language)).to eq('spanish') + it 'builds messages with system message and account context' do + service = described_class.new(assistant, config) + messages = service.messages + + expect(messages.first[:role]).to eq('system') + expect(messages.second[:role]).to eq('system') + expect(messages.second[:content]).to include(account.id.to_s) end end describe '#generate_response' do - before do - allow(OpenAI::Client).to receive(:new).and_return(mock_openai_client) - allow(mock_openai_client).to receive(:chat).and_return({ choices: [{ message: { content: '{ "result": "Hey" }' } }] }.with_indifferent_access) + let(:service) { described_class.new(assistant, config) } - allow(Captain::Agent).to receive(:new).and_return(mock_captain_agent) - allow(mock_captain_agent).to receive(:execute).and_return(true) - allow(mock_captain_agent).to receive(:register_tool).and_return(true) + it 'adds user input to messages when present' do + expect do + service.generate_response('Hello') + end.to(change { service.messages.count }.by(1)) - allow(Captain::Tool).to receive(:new).and_return(mock_captain_tool) - allow(mock_captain_tool).to receive(:register_method).and_return(true) - - allow(account).to receive(:increment_response_usage).and_return(true) + last_message = service.messages.last + expect(last_message[:role]).to eq('user') + expect(last_message[:content]).to eq('Hello') end - it 'increments usage' do - described_class.new(assistant, { previous_messages: ['Hello'], conversation_history: 'Hi' }).generate_response('Hey') - expect(account).to have_received(:increment_response_usage).once + it 'does not add user input to messages when blank' do + expect do + service.generate_response('') + end.not_to(change { service.messages.count }) end - it 'includes language in system message' do - service = described_class.new(assistant, { - previous_messages: [], - conversation_history: '', - language: 'spanish' - }) + it 'returns the response from request_chat_completion' do + expect(service.generate_response('Hello')).to eq({ 'content' => 'Hey' }) + end - allow(Captain::Llm::SystemPromptsService).to receive(:copilot_response_generator) - .with(assistant.config['product_name'], 'spanish') - .and_return('Spanish system prompt') + context 'when response contains tool calls' do + before do + allow(mock_openai_client).to receive(:chat).and_return( + { + choices: [{ message: { 'tool_calls' => tool_calls } }] + }.with_indifferent_access, + { + choices: [{ message: { content: '{ "content": "Tool response processed" }' } }] + }.with_indifferent_access + ) + end - system_message = service.send(:system_message) - expect(system_message[:content]).to eq('Spanish system prompt') + context 'when tool call is valid' do + let(:tool_calls) do + [{ + 'id' => 'call_123', + 'function' => { + 'name' => 'get_conversation', + 'arguments' => "{ \"conversation_id\": #{conversation.display_id} }" + } + }] + end + + it 'processes tool calls and appends them to messages' do + result = service.generate_response("Find conversation #{conversation.id}") + + expect(result).to eq({ 'content' => 'Tool response processed' }) + expect(service.messages).to include( + { role: 'assistant', tool_calls: tool_calls } + ) + expect(service.messages).to include( + { + role: 'tool', tool_call_id: 'call_123', content: conversation.to_llm_text + } + ) + + expect(result).to eq({ 'content' => 'Tool response processed' }) + end + end + + context 'when tool call is invalid' do + let(:tool_calls) do + [{ + 'id' => 'call_123', + 'function' => { + 'name' => 'get_settings', + 'arguments' => '{}' + } + }] + end + + it 'handles invalid tool calls' do + result = service.generate_response('Find settings') + + expect(result).to eq({ 'content' => 'Tool response processed' }) + expect(service.messages).to include( + { + role: 'assistant', tool_calls: tool_calls + } + ) + expect(service.messages).to include( + { + role: 'tool', + tool_call_id: 'call_123', + content: 'Tool not available' + } + ) + end + end end end - describe '#execute' do - before do - allow(OpenAI::Client).to receive(:new).and_return(mock_openai_client) - allow(mock_openai_client).to receive(:chat).and_return({ choices: [{ message: { content: '{ "result": "Hey" }' } }] }.with_indifferent_access) - - allow(Captain::Agent).to receive(:new).and_return(mock_captain_agent) - allow(mock_captain_agent).to receive(:execute).and_return(true) - allow(mock_captain_agent).to receive(:register_tool).and_return(true) - - allow(Captain::Tool).to receive(:new).and_return(mock_captain_tool) - allow(mock_captain_tool).to receive(:register_method).and_return(true) - - allow(account).to receive(:increment_response_usage).and_return(true) + describe '#setup_user' do + it 'sets user when user_id is present in config' do + service = described_class.new(assistant, { user_id: user.id }) + expect(service.user).to eq(user) end - it 'increments usage' do - described_class.new(assistant, { previous_messages: ['Hello'], conversation_history: 'Hi' }).generate_response('Hey') - expect(account).to have_received(:increment_response_usage).once + it 'does not set user when user_id is not present in config' do + service = described_class.new(assistant, {}) + expect(service.user).to be_nil + end + end + + describe '#setup_message_history' do + context 'when copilot_thread_id is present' do + it 'finds the copilot thread and sets previous history from it' do + service = described_class.new(assistant, { copilot_thread_id: copilot_thread.id }) + + expect(service.copilot_thread).to eq(copilot_thread) + expect(service.previous_history).to eq previous_history + end + end + + context 'when copilot_thread_id is not present' do + it 'uses previous_history from config if present' do + custom_history = [{ role: 'user', content: 'Custom message' }] + service = described_class.new(assistant, { previous_history: custom_history }) + + expect(service.copilot_thread).to be_nil + expect(service.previous_history).to eq(custom_history) + end + + it 'uses empty array if previous_history is not present in config' do + service = described_class.new(assistant, {}) + + expect(service.copilot_thread).to be_nil + expect(service.previous_history).to eq([]) + end + end + end + + describe '#build_messages' do + it 'includes system message and account context' do + service = described_class.new(assistant, {}) + messages = service.messages + + expect(messages.first[:role]).to eq('system') + expect(messages.second[:role]).to eq('system') + expect(messages.second[:content]).to include(account.id.to_s) + end + + it 'includes previous history when present' do + custom_history = [{ role: 'user', content: 'Custom message' }] + service = described_class.new(assistant, { previous_history: custom_history }) + messages = service.messages + + expect(messages.count).to be >= 3 + expect(messages.any? { |m| m[:content] == 'Custom message' }).to be true + end + + it 'includes current viewing history when conversation_id is present' do + service = described_class.new(assistant, { conversation_id: conversation.display_id }) + messages = service.messages + + viewing_history = messages.find { |m| m[:content].include?('You are currently viewing the conversation') } + expect(viewing_history).not_to be_nil + expect(viewing_history[:content]).to include(conversation.display_id.to_s) + expect(viewing_history[:content]).to include(contact.id.to_s) + end + end + + describe '#persist_message' do + context 'when copilot_thread is present' do + it 'creates a copilot message' do + allow(mock_openai_client).to receive(:chat).and_return({ + choices: [{ message: { content: '{ "content": "Hey" }' } }] + }.with_indifferent_access) + + expect do + described_class.new(assistant, { copilot_thread_id: copilot_thread.id }).generate_response('Hello') + end.to change(CopilotMessage, :count).by(1) + + last_message = CopilotMessage.last + expect(last_message.message_type).to eq('assistant') + expect(last_message.message['content']).to eq('Hey') + end + end + + context 'when copilot_thread is not present' do + it 'does not create a copilot message' do + allow(mock_openai_client).to receive(:chat).and_return({ + choices: [{ message: { content: '{ "content": "Hey" }' } }] + }.with_indifferent_access) + + expect do + described_class.new(assistant, {}).generate_response('Hello') + end.not_to(change(CopilotMessage, :count)) + end end end end diff --git a/spec/enterprise/services/captain/tool_registry_service_spec.rb b/spec/enterprise/services/captain/tool_registry_service_spec.rb new file mode 100644 index 000000000..c8d97fe3a --- /dev/null +++ b/spec/enterprise/services/captain/tool_registry_service_spec.rb @@ -0,0 +1,112 @@ +require 'rails_helper' + +# Test tool implementation +class TestTool < Captain::Tools::BaseService + attr_accessor :tool_active + + def initialize(assistant, user: nil) + super + @tool_active = true + end + + def name + 'test_tool' + end + + def description + 'A test tool for specs' + end + + def parameters + { + type: 'object', + properties: { + test_param: { + type: 'string' + } + } + } + end + + def execute(*args) + args + end + + def active? + @tool_active + end +end + +RSpec.describe Captain::ToolRegistryService do + let(:assistant) { create(:captain_assistant) } + let(:service) { described_class.new(assistant) } + + describe '#initialize' do + it 'initializes with empty tools and registered_tools' do + expect(service.tools).to be_empty + expect(service.registered_tools).to be_empty + end + end + + describe '#register_tool' do + let(:tool_class) { TestTool } + + context 'when tool is active' do + it 'registers a new tool' do + service.register_tool(tool_class) + expect(service.tools['test_tool']).to be_a(TestTool) + expect(service.registered_tools).to include( + { + type: 'function', + function: { + name: 'test_tool', + description: 'A test tool for specs', + parameters: { + type: 'object', + properties: { + test_param: { + type: 'string' + } + } + } + } + } + ) + end + end + + context 'when tool is inactive' do + it 'does not register the tool' do + tool = tool_class.new(assistant) + tool.tool_active = false + allow(tool_class).to receive(:new).and_return(tool) + + service.register_tool(tool_class) + + expect(service.tools['test_tool']).to be_nil + expect(service.registered_tools).to be_empty + end + end + end + + describe 'method_missing' do + let(:tool_class) { TestTool } + + before do + service.register_tool(tool_class) + end + + context 'when method corresponds to a registered tool' do + it 'executes the tool with given arguments' do + result = service.test_tool(test_param: 'arg1') + expect(result).to eq([{ test_param: 'arg1' }]) + end + end + + context 'when method does not correspond to a registered tool' do + it 'raises NoMethodError' do + expect { service.unknown_tool }.to raise_error(NoMethodError) + end + end + end +end diff --git a/spec/enterprise/services/captain/tools/copilot/get_article_service_spec.rb b/spec/enterprise/services/captain/tools/copilot/get_article_service_spec.rb new file mode 100644 index 000000000..72f4e1cb4 --- /dev/null +++ b/spec/enterprise/services/captain/tools/copilot/get_article_service_spec.rb @@ -0,0 +1,112 @@ +require 'rails_helper' + +RSpec.describe Captain::Tools::Copilot::GetArticleService do + let(:account) { create(:account) } + let(:user) { create(:user, account: account) } + let(:assistant) { create(:captain_assistant, account: account) } + let(:service) { described_class.new(assistant, user: user) } + + describe '#name' do + it 'returns the correct service name' do + expect(service.name).to eq('get_article') + end + end + + describe '#description' do + it 'returns the service description' do + expect(service.description).to eq('Get details of an article including its content and metadata') + end + end + + describe '#parameters' do + it 'returns the expected parameter schema' do + expect(service.parameters).to eq( + { + type: 'object', + properties: { + article_id: { + type: 'number', + description: 'The ID of the article to retrieve' + } + }, + required: %w[article_id] + } + ) + end + end + + describe '#active?' do + context 'when user is an admin' do + let(:user) { create(:user, :administrator, account: account) } + let(:assistant) { create(:captain_assistant, account: account) } + + it 'returns true' do + expect(service.active?).to be true + end + end + + context 'when user has custom role with knowledge_base_manage permission' do + let(:user) { create(:user, account: account) } + let(:assistant) { create(:captain_assistant, account: account) } + let(:custom_role) { create(:custom_role, account: account, permissions: ['knowledge_base_manage']) } + + before do + account_user = AccountUser.find_by(user: user, account: account) + account_user.update(role: :agent, custom_role: custom_role) + end + + it 'returns true' do + expect(service.active?).to be true + end + end + + context 'when user has custom role without knowledge_base_manage permission' do + let(:user) { create(:user, account: account) } + let(:assistant) { create(:captain_assistant, account: account) } + let(:custom_role) { create(:custom_role, account: account, permissions: []) } + + before do + account_user = AccountUser.find_by(user: user, account: account) + account_user.update(role: :agent, custom_role: custom_role) + end + + it 'returns false' do + expect(service.active?).to be false + end + end + end + + describe '#execute' do + context 'when article_id is blank' do + it 'returns error message' do + expect(service.execute({})).to eq('Missing required parameters') + end + end + + context 'when article is not found' do + it 'returns not found message' do + expect(service.execute({ 'article_id' => 999 })).to eq('Article not found') + end + end + + context 'when article exists' do + let(:portal) { create(:portal, account: account) } + let(:article) { create(:article, account: account, portal: portal, author: user, title: 'Test Article', content: 'Content') } + + it 'returns the article in llm text format' do + result = service.execute({ 'article_id' => article.id }) + expect(result).to eq(article.to_llm_text) + end + + context 'when article belongs to different account' do + let(:other_account) { create(:account) } + let(:other_portal) { create(:portal, account: other_account) } + let(:other_article) { create(:article, account: other_account, portal: other_portal, author: user, title: 'Other Article') } + + it 'returns not found message' do + expect(service.execute({ 'article_id' => other_article.id })).to eq('Article not found') + end + end + end + end +end diff --git a/spec/enterprise/services/captain/tools/copilot/get_contact_service_spec.rb b/spec/enterprise/services/captain/tools/copilot/get_contact_service_spec.rb new file mode 100644 index 000000000..de319bfa1 --- /dev/null +++ b/spec/enterprise/services/captain/tools/copilot/get_contact_service_spec.rb @@ -0,0 +1,110 @@ +require 'rails_helper' + +RSpec.describe Captain::Tools::Copilot::GetContactService do + let(:account) { create(:account) } + let(:user) { create(:user, account: account) } + let(:assistant) { create(:captain_assistant, account: account) } + let(:service) { described_class.new(assistant, user: user) } + + describe '#name' do + it 'returns the correct service name' do + expect(service.name).to eq('get_contact') + end + end + + describe '#description' do + it 'returns the service description' do + expect(service.description).to eq('Get details of a contact including their profile information') + end + end + + describe '#parameters' do + it 'returns the expected parameter schema' do + expect(service.parameters).to eq( + { + type: 'object', + properties: { + contact_id: { + type: 'number', + description: 'The ID of the contact to retrieve' + } + }, + required: %w[contact_id] + } + ) + end + end + + describe '#active?' do + context 'when user is an admin' do + let(:user) { create(:user, :administrator, account: account) } + let(:assistant) { create(:captain_assistant, account: account) } + + it 'returns true' do + expect(service.active?).to be true + end + end + + context 'when user has custom role with contact_manage permission' do + let(:user) { create(:user, account: account) } + let(:assistant) { create(:captain_assistant, account: account) } + let(:custom_role) { create(:custom_role, account: account, permissions: ['contact_manage']) } + + before do + account_user = AccountUser.find_by(user: user, account: account) + account_user.update(role: :agent, custom_role: custom_role) + end + + it 'returns true' do + expect(service.active?).to be true + end + end + + context 'when user has custom role without contact_manage permission' do + let(:user) { create(:user, account: account) } + let(:assistant) { create(:captain_assistant, account: account) } + let(:custom_role) { create(:custom_role, account: account, permissions: []) } + + before do + account_user = AccountUser.find_by(user: user, account: account) + account_user.update(role: :agent, custom_role: custom_role) + end + + it 'returns false' do + expect(service.active?).to be false + end + end + end + + describe '#execute' do + context 'when contact_id is blank' do + it 'returns error message' do + expect(service.execute({})).to eq('Missing required parameters') + end + end + + context 'when contact is not found' do + it 'returns not found message' do + expect(service.execute({ 'contact_id' => 999 })).to eq('Contact not found') + end + end + + context 'when contact exists' do + let(:contact) { create(:contact, account: account) } + + it 'returns the contact in llm text format' do + result = service.execute({ 'contact_id' => contact.id }) + expect(result).to eq(contact.to_llm_text) + end + + context 'when contact belongs to different account' do + let(:other_account) { create(:account) } + let(:other_contact) { create(:contact, account: other_account) } + + it 'returns not found message' do + expect(service.execute({ 'contact_id' => other_contact.id })).to eq('Contact not found') + end + end + end + end +end diff --git a/spec/enterprise/services/captain/tools/copilot/get_conversation_service_spec.rb b/spec/enterprise/services/captain/tools/copilot/get_conversation_service_spec.rb new file mode 100644 index 000000000..4d7f1adc7 --- /dev/null +++ b/spec/enterprise/services/captain/tools/copilot/get_conversation_service_spec.rb @@ -0,0 +1,142 @@ +require 'rails_helper' + +RSpec.describe Captain::Tools::Copilot::GetConversationService do + let(:account) { create(:account) } + let(:user) { create(:user, account: account) } + let(:assistant) { create(:captain_assistant, account: account) } + let(:service) { described_class.new(assistant, user: user) } + + describe '#name' do + it 'returns the correct service name' do + expect(service.name).to eq('get_conversation') + end + end + + describe '#description' do + it 'returns the service description' do + expect(service.description).to eq('Get details of a conversation including messages and contact information') + end + end + + describe '#parameters' do + it 'returns the expected parameter schema' do + expect(service.parameters).to eq( + { + type: 'object', + properties: { + conversation_id: { + type: 'number', + description: 'The ID of the conversation to retrieve' + } + }, + required: %w[conversation_id] + } + ) + end + end + + describe '#active?' do + context 'when user is an admin' do + let(:user) { create(:user, :administrator, account: account) } + let(:assistant) { create(:captain_assistant, account: account) } + + it 'returns true' do + expect(service.active?).to be true + end + end + + context 'when user has custom role with conversation_manage permission' do + let(:user) { create(:user, account: account) } + let(:assistant) { create(:captain_assistant, account: account) } + let(:custom_role) { create(:custom_role, account: account, permissions: ['conversation_manage']) } + + before do + account_user = AccountUser.find_by(user: user, account: account) + account_user.update(role: :agent, custom_role: custom_role) + end + + it 'returns true' do + expect(service.active?).to be true + end + end + + context 'when user has custom role with conversation_unassigned_manage permission' do + let(:user) { create(:user, account: account) } + let(:assistant) { create(:captain_assistant, account: account) } + let(:custom_role) { create(:custom_role, account: account, permissions: ['conversation_unassigned_manage']) } + + before do + account_user = AccountUser.find_by(user: user, account: account) + account_user.update(role: :agent, custom_role: custom_role) + end + + it 'returns true' do + expect(service.active?).to be true + end + end + + context 'when user has custom role with conversation_participating_manage permission' do + let(:user) { create(:user, account: account) } + let(:assistant) { create(:captain_assistant, account: account) } + let(:custom_role) { create(:custom_role, account: account, permissions: ['conversation_participating_manage']) } + + before do + account_user = AccountUser.find_by(user: user, account: account) + account_user.update(role: :agent, custom_role: custom_role) + end + + it 'returns true' do + expect(service.active?).to be true + end + end + + context 'when user has custom role without any conversation permissions' do + let(:user) { create(:user, account: account) } + let(:assistant) { create(:captain_assistant, account: account) } + let(:custom_role) { create(:custom_role, account: account, permissions: []) } + + before do + account_user = AccountUser.find_by(user: user, account: account) + account_user.update(role: :agent, custom_role: custom_role) + end + + it 'returns false' do + expect(service.active?).to be false + end + end + end + + describe '#execute' do + context 'when conversation_id is blank' do + it 'returns error message' do + expect(service.execute({})).to eq('Missing required parameters') + end + end + + context 'when conversation is not found' do + it 'returns not found message' do + expect(service.execute({ 'conversation_id' => 999 })).to eq('Conversation not found') + end + end + + context 'when conversation exists' do + let(:inbox) { create(:inbox, account: account) } + let(:conversation) { create(:conversation, account: account, inbox: inbox) } + + it 'returns the conversation in llm text format' do + result = service.execute({ 'conversation_id' => conversation.display_id }) + expect(result).to eq(conversation.to_llm_text) + end + + context 'when conversation belongs to different account' do + let(:other_account) { create(:account) } + let(:other_inbox) { create(:inbox, account: other_account) } + let(:other_conversation) { create(:conversation, account: other_account, inbox: other_inbox) } + + it 'returns not found message' do + expect(service.execute({ 'conversation_id' => other_conversation.display_id })).to eq('Conversation not found') + end + end + end + end +end diff --git a/spec/enterprise/services/captain/tools/copilot/search_articles_service_spec.rb b/spec/enterprise/services/captain/tools/copilot/search_articles_service_spec.rb new file mode 100644 index 000000000..eb11e0d69 --- /dev/null +++ b/spec/enterprise/services/captain/tools/copilot/search_articles_service_spec.rb @@ -0,0 +1,147 @@ +require 'rails_helper' + +RSpec.describe Captain::Tools::Copilot::SearchArticlesService do + let(:account) { create(:account) } + let(:user) { create(:user, account: account) } + let(:assistant) { create(:captain_assistant, account: account) } + let(:service) { described_class.new(assistant, user: user) } + + describe '#name' do + it 'returns the correct service name' do + expect(service.name).to eq('search_articles') + end + end + + describe '#description' do + it 'returns the service description' do + expect(service.description).to eq('Search articles based on parameters') + end + end + + describe '#parameters' do + it 'returns the expected parameter schema' do + expect(service.parameters).to eq( + { + type: 'object', + properties: { + query: { + type: 'string', + description: 'Search articles by title or content (partial match)' + }, + category_id: { + type: 'number', + description: 'Filter articles by category ID' + }, + status: { + type: 'string', + enum: %w[draft published archived], + description: 'Filter articles by status' + } + }, + required: ['query'] + } + ) + end + end + + describe '#active?' do + context 'when user is an admin' do + let(:user) { create(:user, :administrator, account: account) } + let(:assistant) { create(:captain_assistant, account: account) } + + it 'returns true' do + expect(service.active?).to be true + end + end + + context 'when user is an agent' do + let(:user) { create(:user, account: account) } + let(:assistant) { create(:captain_assistant, account: account) } + + it 'returns true' do + expect(service.active?).to be true + end + end + + context 'when user has custom role with knowledge_base_manage permission' do + let(:user) { create(:user, account: account) } + let(:assistant) { create(:captain_assistant, account: account) } + let(:custom_role) { create(:custom_role, account: account, permissions: ['knowledge_base_manage']) } + + before do + account_user = AccountUser.find_by(user: user, account: account) + account_user.update(role: :agent, custom_role: custom_role) + end + + it 'returns true' do + expect(service.active?).to be true + end + end + + context 'when user has custom role without knowledge_base_manage permission' do + let(:user) { create(:user, account: account) } + let(:assistant) { create(:captain_assistant, account: account) } + let(:custom_role) { create(:custom_role, account: account, permissions: []) } + + before do + account_user = AccountUser.find_by(user: user, account: account) + account_user.update(role: :agent, custom_role: custom_role) + end + + it 'returns false' do + expect(service.active?).to be false + end + end + end + + describe '#execute' do + context 'when query is blank' do + it 'returns error message' do + expect(service.execute({})).to eq('Missing required parameters') + end + end + + context 'when no articles are found' do + it 'returns no articles found message' do + expect(service.execute({ 'query' => 'test' })).to eq('No articles found') + end + end + + context 'when articles are found' do + let(:portal) { create(:portal, account: account) } + let!(:article1) { create(:article, account: account, portal: portal, author: user, title: 'Test Article 1', content: 'Content 1') } + let!(:article2) { create(:article, account: account, portal: portal, author: user, title: 'Test Article 2', content: 'Content 2') } + + it 'returns formatted articles with count' do + result = service.execute({ 'query' => 'Test' }) + expect(result).to include('Total number of articles: 2') + expect(result).to include(article1.to_llm_text) + expect(result).to include(article2.to_llm_text) + end + + context 'when filtered by category' do + let(:category) { create(:category, slug: 'test-category', portal: portal, account: account) } + let!(:article3) { create(:article, account: account, portal: portal, author: user, category: category, title: 'Test Article 3') } + + it 'returns only articles from the specified category' do + result = service.execute({ 'query' => 'Test', 'category_id' => category.id }) + expect(result).to include('Total number of articles: 1') + expect(result).to include(article3.to_llm_text) + expect(result).not_to include(article1.to_llm_text) + expect(result).not_to include(article2.to_llm_text) + end + end + + context 'when filtered by status' do + let!(:article3) { create(:article, account: account, portal: portal, author: user, title: 'Test Article 3', status: 'published') } + let!(:article4) { create(:article, account: account, portal: portal, author: user, title: 'Test Article 4', status: 'draft') } + + it 'returns only articles with the specified status' do + result = service.execute({ 'query' => 'Test', 'status' => 'published' }) + expect(result).to include(article3.to_llm_text) + expect(result).not_to include(article4.to_llm_text) + end + end + end + end +end diff --git a/spec/enterprise/services/captain/tools/copilot/search_contacts_service_spec.rb b/spec/enterprise/services/captain/tools/copilot/search_contacts_service_spec.rb new file mode 100644 index 000000000..f54b2eddf --- /dev/null +++ b/spec/enterprise/services/captain/tools/copilot/search_contacts_service_spec.rb @@ -0,0 +1,113 @@ +require 'rails_helper' + +RSpec.describe Captain::Tools::Copilot::SearchContactsService do + let(:account) { create(:account) } + let(:user) { create(:user, account: account) } + let(:assistant) { create(:captain_assistant, account: account) } + let(:service) { described_class.new(assistant, user: user) } + + describe '#name' do + it 'returns the correct service name' do + expect(service.name).to eq('search_contacts') + end + end + + describe '#description' do + it 'returns the service description' do + expect(service.description).to eq('Search contacts based on query parameters') + end + end + + describe '#parameters' do + it 'returns the expected parameter schema' do + expect(service.parameters).to eq( + { + type: 'object', + properties: { + email: { + type: 'string', + description: 'Filter contacts by email' + }, + phone_number: { + type: 'string', + description: 'Filter contacts by phone number' + }, + name: { + type: 'string', + description: 'Filter contacts by name (partial match)' + } + }, + required: [] + } + ) + end + end + + describe '#active?' do + context 'when user has contact_manage permission' do + let(:user) { create(:user, account: account) } + let(:assistant) { create(:captain_assistant, account: account) } + let(:custom_role) { create(:custom_role, account: account, permissions: ['contact_manage']) } + + before do + account_user = AccountUser.find_by(user: user, account: account) + account_user.update(role: :agent, custom_role: custom_role) + end + + it 'returns true' do + expect(service.active?).to be true + end + end + + context 'when user does not have contact_manage permission' do + let(:user) { create(:user, account: account) } + let(:assistant) { create(:captain_assistant, account: account) } + let(:custom_role) { create(:custom_role, account: account, permissions: []) } + + before do + account_user = AccountUser.find_by(user: user, account: account) + account_user.update(role: :agent, custom_role: custom_role) + end + + it 'returns false' do + expect(service.active?).to be false + end + end + end + + describe '#execute' do + context 'when contacts are found' do + let(:contact1) { create(:contact, account: account, email: 'test1@example.com', name: 'Test Contact 1', phone_number: '+1234567890') } + let(:contact2) { create(:contact, account: account, email: 'test2@example.com', name: 'Test Contact 2', phone_number: '+1234567891') } + + before do + contact1 + contact2 + end + + it 'returns contacts when filtered by email' do + result = service.execute({ 'email' => 'test1@example.com' }) + expect(result).to include(contact1.to_llm_text) + expect(result).not_to include(contact2.to_llm_text) + end + + it 'returns contacts when filtered by phone number' do + result = service.execute({ 'phone_number' => '+1234567890' }) + expect(result).to include(contact1.to_llm_text) + expect(result).not_to include(contact2.to_llm_text) + end + + it 'returns contacts when filtered by name' do + result = service.execute({ 'name' => 'Contact 1' }) + expect(result).to include(contact1.to_llm_text) + expect(result).not_to include(contact2.to_llm_text) + end + + it 'returns all matching contacts when no filters are provided' do + result = service.execute({}) + expect(result).to include(contact1.to_llm_text) + expect(result).to include(contact2.to_llm_text) + end + end + end +end diff --git a/spec/enterprise/services/captain/tools/copilot/search_conversations_service_spec.rb b/spec/enterprise/services/captain/tools/copilot/search_conversations_service_spec.rb new file mode 100644 index 000000000..ab0865bc2 --- /dev/null +++ b/spec/enterprise/services/captain/tools/copilot/search_conversations_service_spec.rb @@ -0,0 +1,125 @@ +require 'rails_helper' + +RSpec.describe Captain::Tools::Copilot::SearchConversationsService do + let(:account) { create(:account) } + let(:user) { create(:user, role: 'administrator', account: account) } + let(:assistant) { create(:captain_assistant, account: account) } + let(:service) { described_class.new(assistant, user: user) } + + describe '#name' do + it 'returns the correct service name' do + expect(service.name).to eq('search_conversations') + end + end + + describe '#description' do + it 'returns the service description' do + expect(service.description).to eq('Search conversations based on parameters') + end + end + + describe '#parameters' do + it 'returns the correct parameter schema' do + params = service.parameters + expect(params[:type]).to eq('object') + expect(params[:properties]).to include(:contact_id, :status, :priority) + end + end + + describe '#active?' do + context 'when user has conversation_manage permission' do + let(:custom_role) { create(:custom_role, account: account, permissions: ['conversation_manage']) } + let(:user) { create(:user, account: account) } + + before do + account_user = AccountUser.find_by(user: user, account: account) + account_user.update(role: :agent, custom_role: custom_role) + end + + it 'returns true' do + expect(service.active?).to be true + end + end + + context 'when user has conversation_unassigned_manage permission' do + let(:custom_role) { create(:custom_role, account: account, permissions: ['conversation_unassigned_manage']) } + let(:user) { create(:user, account: account) } + + before do + account_user = AccountUser.find_by(user: user, account: account) + account_user.update(role: :agent, custom_role: custom_role) + end + + it 'returns true' do + expect(service.active?).to be true + end + end + + context 'when user has conversation_participating_manage permission' do + let(:custom_role) { create(:custom_role, account: account, permissions: ['conversation_participating_manage']) } + let(:user) { create(:user, account: account) } + + before do + account_user = AccountUser.find_by(user: user, account: account) + account_user.update(role: :agent, custom_role: custom_role) + end + + it 'returns true' do + expect(service.active?).to be true + end + end + + context 'when user has no relevant conversation permissions' do + let(:custom_role) { create(:custom_role, account: account, permissions: []) } + let(:user) { create(:user, account: account) } + + before do + account_user = AccountUser.find_by(user: user, account: account) + account_user.update(role: :agent, custom_role: custom_role) + end + + it 'returns false' do + expect(service.active?).to be false + end + end + end + + describe '#execute' do + let(:contact) { create(:contact, account: account) } + let!(:open_conversation) { create(:conversation, account: account, contact: contact, status: 'open', priority: 'high') } + let!(:resolved_conversation) { create(:conversation, account: account, status: 'resolved', priority: 'low') } + + it 'returns all conversations when no filters are applied' do + result = service.execute({}) + expect(result).to include('Total number of conversations: 2') + expect(result).to include(open_conversation.to_llm_text(include_contact_details: true)) + expect(result).to include(resolved_conversation.to_llm_text(include_contact_details: true)) + end + + it 'filters conversations by status' do + result = service.execute({ 'status' => 'open' }) + expect(result).to include('Total number of conversations: 1') + expect(result).to include(open_conversation.to_llm_text(include_contact_details: true)) + expect(result).not_to include(resolved_conversation.to_llm_text(include_contact_details: true)) + end + + it 'filters conversations by contact_id' do + result = service.execute({ 'contact_id' => contact.id }) + expect(result).to include('Total number of conversations: 1') + expect(result).to include(open_conversation.to_llm_text(include_contact_details: true)) + expect(result).not_to include(resolved_conversation.to_llm_text(include_contact_details: true)) + end + + it 'filters conversations by priority' do + result = service.execute({ 'priority' => 'high' }) + expect(result).to include('Total number of conversations: 1') + expect(result).to include(open_conversation.to_llm_text(include_contact_details: true)) + expect(result).not_to include(resolved_conversation.to_llm_text(include_contact_details: true)) + end + + it 'returns appropriate message when no conversations are found' do + result = service.execute({ 'status' => 'snoozed' }) + expect(result).to eq('No conversations found') + end + end +end diff --git a/spec/enterprise/services/captain/tools/copilot/search_linear_issues_service_spec.rb b/spec/enterprise/services/captain/tools/copilot/search_linear_issues_service_spec.rb new file mode 100644 index 000000000..f987b7a6a --- /dev/null +++ b/spec/enterprise/services/captain/tools/copilot/search_linear_issues_service_spec.rb @@ -0,0 +1,146 @@ +require 'rails_helper' + +RSpec.describe Captain::Tools::Copilot::SearchLinearIssuesService do + let(:account) { create(:account) } + let(:assistant) { create(:captain_assistant, account: account) } + let(:user) { create(:user, account: account) } + let(:service) { described_class.new(assistant, user: user) } + + describe '#name' do + it 'returns the correct service name' do + expect(service.name).to eq('search_linear_issues') + end + end + + describe '#description' do + it 'returns the service description' do + expect(service.description).to eq('Search Linear issues based on a search term') + end + end + + describe '#parameters' do + it 'returns the expected parameter schema' do + expect(service.parameters).to eq( + { + type: 'object', + properties: { + term: { + type: 'string', + description: 'The search term to find Linear issues' + } + }, + required: %w[term] + } + ) + end + end + + describe '#active?' do + context 'when Linear integration is enabled' do + before do + create(:integrations_hook, :linear, account: account) + end + + context 'when user is present' do + it 'returns true' do + expect(service.active?).to be true + end + end + + context 'when user is not present' do + let(:service) { described_class.new(assistant) } + + it 'returns false' do + expect(service.active?).to be false + end + end + end + + context 'when Linear integration is not enabled' do + context 'when user is present' do + it 'returns false' do + expect(service.active?).to be false + end + end + + context 'when user is not present' do + let(:service) { described_class.new(assistant) } + + it 'returns false' do + expect(service.active?).to be false + end + end + end + end + + describe '#execute' do + context 'when Linear integration is not enabled' do + it 'returns error message' do + expect(service.execute({ 'term' => 'test' })).to eq('Linear integration is not enabled') + end + end + + context 'when Linear integration is enabled' do + let(:linear_service) { instance_double(Integrations::Linear::ProcessorService) } + + before do + create(:integrations_hook, :linear, account: account) + allow(Integrations::Linear::ProcessorService).to receive(:new).and_return(linear_service) + end + + context 'when term is blank' do + it 'returns error message' do + expect(service.execute({ 'term' => '' })).to eq('Missing required parameters') + end + end + + context 'when search returns error' do + before do + allow(linear_service).to receive(:search_issue).and_return({ error: 'API Error' }) + end + + it 'returns the error message' do + expect(service.execute({ 'term' => 'test' })).to eq('API Error') + end + end + + context 'when search returns no issues' do + before do + allow(linear_service).to receive(:search_issue).and_return({ data: [] }) + end + + it 'returns no issues found message' do + expect(service.execute({ 'term' => 'test' })).to eq('No issues found, I should try another similar search term') + end + end + + context 'when search returns issues' do + let(:issues) do + [{ + 'title' => 'Test Issue', + 'id' => 'TEST-123', + 'state' => { 'name' => 'In Progress' }, + 'priority' => 4, + 'assignee' => { 'name' => 'John Doe' }, + 'description' => 'Test description' + }] + end + + before do + allow(linear_service).to receive(:search_issue).and_return({ data: issues }) + end + + it 'returns formatted issues' do + result = service.execute({ 'term' => 'test' }) + expect(result).to include('Total number of issues: 1') + expect(result).to include('Title: Test Issue') + expect(result).to include('ID: TEST-123') + expect(result).to include('State: In Progress') + expect(result).to include('Priority: Low') + expect(result).to include('Assignee: John Doe') + expect(result).to include('Description: Test description') + end + end + end + end +end diff --git a/spec/enterprise/services/captain/tools/search_documentation_service_spec.rb b/spec/enterprise/services/captain/tools/search_documentation_service_spec.rb new file mode 100644 index 000000000..9f5586e6b --- /dev/null +++ b/spec/enterprise/services/captain/tools/search_documentation_service_spec.rb @@ -0,0 +1,77 @@ +require 'rails_helper' + +RSpec.describe Captain::Tools::SearchDocumentationService do + let(:assistant) { create(:captain_assistant) } + let(:service) { described_class.new(assistant) } + let(:question) { 'How to create a new account?' } + let(:answer) { 'You can create a new account by clicking on the Sign Up button.' } + let(:external_link) { 'https://example.com/docs/create-account' } + + describe '#name' do + it 'returns the correct service name' do + expect(service.name).to eq('search_documentation') + end + end + + describe '#description' do + it 'returns the service description' do + expect(service.description).to eq('Search and retrieve documentation from knowledge base') + end + end + + describe '#parameters' do + it 'returns the required parameters schema' do + expected_schema = { + type: 'object', + properties: { + search_query: { + type: 'string', + description: 'The search query to look up in the documentation.' + } + }, + required: ['search_query'] + } + + expect(service.parameters).to eq(expected_schema) + end + end + + describe '#execute' do + let!(:response) do + create( + :captain_assistant_response, + assistant: assistant, + question: question, + answer: answer, + status: 'approved' + ) + end + + let(:documentable) { create(:captain_document, external_link: external_link) } + + context 'when matching responses exist' do + before do + response.update(documentable: documentable) + allow(Captain::AssistantResponse).to receive(:search).with(question).and_return([response]) + end + + it 'returns formatted responses for the search query' do + result = service.execute({ 'search_query' => question }) + + expect(result).to include(question) + expect(result).to include(answer) + expect(result).to include(external_link) + end + end + + context 'when no matching responses exist' do + before do + allow(Captain::AssistantResponse).to receive(:search).with(question).and_return([]) + end + + it 'returns an empty string' do + expect(service.execute({ 'search_query' => question })).to eq('No FAQs found for the given query') + end + end + end +end diff --git a/spec/enterprise/services/enterprise/clearbit_lookup_service_spec.rb b/spec/enterprise/services/enterprise/clearbit_lookup_service_spec.rb index 725aa6523..d746b9a83 100644 --- a/spec/enterprise/services/enterprise/clearbit_lookup_service_spec.rb +++ b/spec/enterprise/services/enterprise/clearbit_lookup_service_spec.rb @@ -49,6 +49,10 @@ RSpec.describe Enterprise::ClearbitLookupService do end context 'when Clearbit is not enabled' do + before do + GlobalConfig.clear_cache + end + it 'returns nil without making an API call' do with_modified_env CLEARBIT_API_KEY: nil do expect(Net::HTTP).not_to receive(:start) diff --git a/spec/enterprise/services/enterprise/conversations/permission_filter_service_spec.rb b/spec/enterprise/services/enterprise/conversations/permission_filter_service_spec.rb index b26832faf..0cfec97eb 100644 --- a/spec/enterprise/services/enterprise/conversations/permission_filter_service_spec.rb +++ b/spec/enterprise/services/enterprise/conversations/permission_filter_service_spec.rb @@ -9,6 +9,8 @@ RSpec.describe Enterprise::Conversations::PermissionFilterService do let(:admin) { create(:user, account: account, role: :administrator) } let(:agent) { create(:user, account: account, role: :agent) } let!(:inbox) { create(:inbox, account: account) } + let!(:inbox2) { create(:inbox, account: account) } + let!(:another_inbox_conversation) { create(:conversation, account: account, inbox: inbox2) } # This inbox_member is used to establish the agent's access to the inbox before { create(:inbox_member, user: agent, inbox: inbox) } @@ -25,16 +27,14 @@ RSpec.describe Enterprise::Conversations::PermissionFilterService do expect(result).to include(assigned_conversation) expect(result).to include(unassigned_conversation) expect(result).to include(another_assigned_conversation) - expect(result.count).to eq(3) + expect(result.count).to eq(4) end end context 'when user is a regular agent' do it 'returns all conversations in assigned inboxes' do - inbox_ids = agent.inboxes.where(account_id: account.id).pluck(:id) - result = Conversations::PermissionFilterService.new( - account.conversations.where(inbox_id: inbox_ids), + account.conversations, agent, account ).perform @@ -42,6 +42,7 @@ RSpec.describe Enterprise::Conversations::PermissionFilterService do expect(result).to include(assigned_conversation) expect(result).to include(unassigned_conversation) expect(result).to include(another_assigned_conversation) + expect(result).not_to include(another_inbox_conversation) expect(result.count).to eq(3) end end @@ -52,7 +53,7 @@ RSpec.describe Enterprise::Conversations::PermissionFilterService do # Create a new isolated test environment test_account = create(:account) test_inbox = create(:inbox, account: test_account) - + test_inbox2 = create(:inbox, account: test_account) # Create test agent test_agent = create(:user, account: test_account, role: :agent) create(:inbox_member, user: test_agent, inbox: test_inbox) @@ -66,6 +67,7 @@ RSpec.describe Enterprise::Conversations::PermissionFilterService do assigned_conversation = create(:conversation, account: test_account, inbox: test_inbox, assignee: test_agent) unassigned_conversation = create(:conversation, account: test_account, inbox: test_inbox, assignee: nil) other_assigned_conversation = create(:conversation, account: test_account, inbox: test_inbox, assignee: create(:user, account: test_account)) + other_inbox_conversation = create(:conversation, account: test_account, inbox: test_inbox2, assignee: nil) # Run the test result = Conversations::PermissionFilterService.new( @@ -79,6 +81,7 @@ RSpec.describe Enterprise::Conversations::PermissionFilterService do expect(result).to include(assigned_conversation) expect(result).to include(unassigned_conversation) expect(result).to include(other_assigned_conversation) + expect(result).not_to include(other_inbox_conversation) end end @@ -87,6 +90,7 @@ RSpec.describe Enterprise::Conversations::PermissionFilterService do # Create a new isolated test environment test_account = create(:account) test_inbox = create(:inbox, account: test_account) + test_inbox2 = create(:inbox, account: test_account) # Create test agent test_agent = create(:user, account: test_account, role: :agent) @@ -101,6 +105,7 @@ RSpec.describe Enterprise::Conversations::PermissionFilterService do # Create some conversations other_conversation = create(:conversation, account: test_account, inbox: test_inbox) assigned_conversation = create(:conversation, account: test_account, inbox: test_inbox, assignee: test_agent) + other_inbox_conversation = create(:conversation, account: test_account, inbox: test_inbox2, assignee: nil) # Run the test result = Conversations::PermissionFilterService.new( @@ -114,6 +119,7 @@ RSpec.describe Enterprise::Conversations::PermissionFilterService do expect(result.first.assignee).to eq(test_agent) expect(result).to include(assigned_conversation) expect(result).not_to include(other_conversation) + expect(result).not_to include(other_inbox_conversation) end end @@ -122,6 +128,7 @@ RSpec.describe Enterprise::Conversations::PermissionFilterService do # Create a new isolated test environment test_account = create(:account) test_inbox = create(:inbox, account: test_account) + test_inbox2 = create(:inbox, account: test_account) # Create test agent test_agent = create(:user, account: test_account, role: :agent) @@ -137,6 +144,7 @@ RSpec.describe Enterprise::Conversations::PermissionFilterService do assigned_conversation = create(:conversation, account: test_account, inbox: test_inbox, assignee: test_agent) unassigned_conversation = create(:conversation, account: test_account, inbox: test_inbox, assignee: nil) other_assigned_conversation = create(:conversation, account: test_account, inbox: test_inbox, assignee: create(:user, account: test_account)) + other_inbox_conversation = create(:conversation, account: test_account, inbox: test_inbox2, assignee: nil) # Run the test result = Conversations::PermissionFilterService.new( @@ -152,6 +160,7 @@ RSpec.describe Enterprise::Conversations::PermissionFilterService do # Should NOT include conversations assigned to others expect(result).not_to include(other_assigned_conversation) + expect(result).not_to include(other_inbox_conversation) end end @@ -160,6 +169,7 @@ RSpec.describe Enterprise::Conversations::PermissionFilterService do # Create a new isolated test environment test_account = create(:account) test_inbox = create(:inbox, account: test_account) + test_inbox2 = create(:inbox, account: test_account) # Create test agent test_agent = create(:user, account: test_account, role: :agent) @@ -176,6 +186,7 @@ RSpec.describe Enterprise::Conversations::PermissionFilterService do assigned_to_agent = create(:conversation, account: test_account, inbox: test_inbox, assignee: test_agent) unassigned_conversation = create(:conversation, account: test_account, inbox: test_inbox, assignee: nil) other_assigned_conversation = create(:conversation, account: test_account, inbox: test_inbox, assignee: create(:user, account: test_account)) + other_inbox_conversation = create(:conversation, account: test_account, inbox: test_inbox2, assignee: nil) # Run the test result = Conversations::PermissionFilterService.new( @@ -191,6 +202,7 @@ RSpec.describe Enterprise::Conversations::PermissionFilterService do expect(result).to include(unassigned_conversation) expect(result).to include(assigned_to_agent) expect(result).not_to include(other_assigned_conversation) + expect(result).not_to include(other_inbox_conversation) end end end diff --git a/spec/factories/articles.rb b/spec/factories/articles.rb index e53549090..b86e422a9 100644 --- a/spec/factories/articles.rb +++ b/spec/factories/articles.rb @@ -1,13 +1,14 @@ FactoryBot.define do factory :article, class: 'Article' do - account_id { 1 } - category_id { 1 } + account + category { nil } + portal locale { 'en' } - author_id { 1 } + association :author, factory: :user title { "#{Faker::Movie.title} #{SecureRandom.hex}" } content { 'MyText' } description { 'MyDescrption' } - status { 1 } + status { :published } views { 0 } end end diff --git a/spec/factories/captain/copilot_message.rb b/spec/factories/captain/copilot_message.rb index 78f9f202e..5d24d0ebf 100644 --- a/spec/factories/captain/copilot_message.rb +++ b/spec/factories/captain/copilot_message.rb @@ -1,9 +1,8 @@ FactoryBot.define do factory :captain_copilot_message, class: 'CopilotMessage' do account - user copilot_thread { association :captain_copilot_thread } message { { content: 'This is a test message' } } - message_type { 'user' } + message_type { 0 } end end diff --git a/spec/factories/captain/copilot_thread.rb b/spec/factories/captain/copilot_thread.rb index fee78a7e7..2c7a4170c 100644 --- a/spec/factories/captain/copilot_thread.rb +++ b/spec/factories/captain/copilot_thread.rb @@ -3,6 +3,6 @@ FactoryBot.define do account user title { Faker::Lorem.sentence } - uuid { SecureRandom.uuid } + assistant { create(:captain_assistant, account: account) } end end diff --git a/spec/factories/categories.rb b/spec/factories/categories.rb index f7becf8fc..f9685b8b0 100644 --- a/spec/factories/categories.rb +++ b/spec/factories/categories.rb @@ -1,9 +1,10 @@ FactoryBot.define do factory :category, class: 'Category' do - portal { portal } + portal name { 'MyString' } description { 'MyText' } position { 1 } + slug { name.parameterize } after(:build) do |category| category.account ||= category.portal.account diff --git a/spec/jobs/internal/delete_accounts_job_spec.rb b/spec/jobs/internal/delete_accounts_job_spec.rb new file mode 100644 index 000000000..514ecf6ab --- /dev/null +++ b/spec/jobs/internal/delete_accounts_job_spec.rb @@ -0,0 +1,44 @@ +require 'rails_helper' + +RSpec.describe Internal::DeleteAccountsJob do + subject(:job) { described_class.perform_later } + + let!(:account_marked_for_deletion) { create(:account) } + let!(:future_deletion_account) { create(:account) } + let!(:active_account) { create(:account) } + let(:account_deletion_service) { instance_double(AccountDeletionService, perform: true) } + + before do + account_marked_for_deletion.update!( + custom_attributes: { + 'marked_for_deletion_at' => 1.day.ago.iso8601, + 'marked_for_deletion_reason' => 'user_requested' + } + ) + + future_deletion_account.update!( + custom_attributes: { + 'marked_for_deletion_at' => 3.days.from_now.iso8601, + 'marked_for_deletion_reason' => 'user_requested' + } + ) + + allow(AccountDeletionService).to receive(:new).and_return(account_deletion_service) + end + + it 'enqueues the job' do + expect { job }.to have_enqueued_job(described_class) + .on_queue('scheduled_jobs') + end + + describe '#perform' do + it 'calls AccountDeletionService for accounts past deletion date' do + described_class.new.perform + + expect(AccountDeletionService).to have_received(:new).with(account: account_marked_for_deletion) + expect(AccountDeletionService).not_to have_received(:new).with(account: future_deletion_account) + expect(AccountDeletionService).not_to have_received(:new).with(account: active_account) + expect(account_deletion_service).to have_received(:perform) + end + end +end diff --git a/spec/lib/custom_markdown_renderer_spec.rb b/spec/lib/custom_markdown_renderer_spec.rb index 550e7b637..23574e8c4 100644 --- a/spec/lib/custom_markdown_renderer_spec.rb +++ b/spec/lib/custom_markdown_renderer_spec.rb @@ -143,6 +143,17 @@ describe CustomMarkdownRenderer do end end + context 'when link is a wistia URL' do + let(:wistia_url) { 'https://chatwoot.wistia.com/medias/kjwjeq6f9i' } + + it 'renders a custom element with Wistia embed code' do + output = render_markdown_link(wistia_url) + expect(output).to include('') + expect(output).to include(' 1.day.ago.iso8601, 'marked_for_deletion_reason' => 'user_requested' }) + end + let(:soft_deleted_users) do + [ + { id: 1, original_email: 'user1@example.com' }, + { id: 2, original_email: 'user2@example.com' } + ] + end + + describe 'account_deleted' do + it 'has the right subject format' do + subject = described_class.new.send(:subject_for, account) + expect(subject).to eq("Account Deletion Notice for #{account.id} - #{account.name}") + end + + it 'includes soft deleted users in meta when provided' do + mailer_instance = described_class.new + allow(mailer_instance).to receive(:params).and_return( + { soft_deleted_users: soft_deleted_users } + ) + + meta = mailer_instance.send(:build_meta, account) + + expect(meta['deleted_user_count']).to eq(2) + expect(meta['soft_deleted_users'].size).to eq(2) + expect(meta['soft_deleted_users'].first['user_id']).to eq('1') + expect(meta['soft_deleted_users'].first['user_email']).to eq('user1@example.com') + end + end +end diff --git a/spec/models/conversation_spec.rb b/spec/models/conversation_spec.rb index 9395f3e55..ad3446a13 100644 --- a/spec/models/conversation_spec.rb +++ b/spec/models/conversation_spec.rb @@ -435,6 +435,20 @@ RSpec.describe Conversation do end end + describe '#create_csat_not_sent_activity_message' do + subject(:create_csat_not_sent_activity_message) { conversation.create_csat_not_sent_activity_message } + + let(:conversation) { create(:conversation) } + + it 'creates CSAT not sent activity message' do + create_csat_not_sent_activity_message + expect(Conversations::ActivityMessageJob) + .to(have_been_enqueued.at_least(:once).with(conversation, { account_id: conversation.account_id, inbox_id: conversation.inbox_id, + message_type: :activity, + content: 'CSAT survey not sent due to outgoing message restrictions' })) + end + end + describe 'unread_messages' do subject(:unread_messages) { conversation.unread_messages } @@ -793,8 +807,8 @@ RSpec.describe Conversation do end context 'when a new conversation is created' do - it 'sets last_activity_at to the created_at time' do - expect(conversation.last_activity_at).to eq(conversation.created_at) + it 'sets last_activity_at to the created_at time (within DB precision)' do + expect(conversation.last_activity_at).to be_within(1.second).of(conversation.created_at) end end diff --git a/spec/models/message_spec.rb b/spec/models/message_spec.rb index 4c66e37ee..b4be3ac5a 100644 --- a/spec/models/message_spec.rb +++ b/spec/models/message_spec.rb @@ -475,4 +475,35 @@ RSpec.describe Message do end end end + + describe '#content' do + let(:conversation) { create(:conversation) } + let(:message) { create(:message, conversation: conversation, content_type: 'input_csat', content: 'Original content') } + + it 'returns original content for web widget inbox' do + allow(message.inbox).to receive(:web_widget?).and_return(true) + expect(message.content).to eq('Original content') + end + + context 'when inbox is not a web widget' do + before do + allow(message.inbox).to receive(:web_widget?).and_return(false) + allow(ENV).to receive(:fetch).with('FRONTEND_URL', nil).and_return('https://app.chatwoot.com') + end + + it 'returns custom message with survey link when csat message is configured' do + allow(message.inbox).to receive(:csat_config).and_return({ 'message' => 'Custom survey message:' }) + expected_content = "Custom survey message: https://app.chatwoot.com/survey/responses/#{conversation.uuid}" + expect(message.content).to eq(expected_content) + end + + it 'returns default message with survey link when no custom csat message' do + allow(message.inbox).to receive(:csat_config).and_return(nil) + allow(I18n).to receive(:t).with('conversations.survey.response', link: "https://app.chatwoot.com/survey/responses/#{conversation.uuid}") + .and_return("Please rate your conversation: https://app.chatwoot.com/survey/responses/#{conversation.uuid}") + expected_content = "Please rate your conversation: https://app.chatwoot.com/survey/responses/#{conversation.uuid}" + expect(message.content).to eq(expected_content) + end + end + end end diff --git a/spec/services/account/sign_up_email_validation_service_spec.rb b/spec/services/account/sign_up_email_validation_service_spec.rb new file mode 100644 index 000000000..3f907f02c --- /dev/null +++ b/spec/services/account/sign_up_email_validation_service_spec.rb @@ -0,0 +1,74 @@ +# frozen_string_literal: true + +require 'rails_helper' + +RSpec.describe Account::SignUpEmailValidationService, type: :service do + let(:service) { described_class.new(email) } + let(:blocked_domains) { "gmail.com\noutlook.com" } + let(:valid_email_address) { instance_double(ValidEmail2::Address, valid?: true, disposable?: false) } + let(:disposable_email_address) { instance_double(ValidEmail2::Address, valid?: true, disposable?: true) } + let(:invalid_email_address) { instance_double(ValidEmail2::Address, valid?: false) } + + before do + allow(GlobalConfigService).to receive(:load).with('BLOCKED_EMAIL_DOMAINS', '').and_return(blocked_domains) + end + + describe '#perform' do + context 'when email is invalid format' do + let(:email) { 'invalid-email' } + + it 'raises InvalidEmail with invalid message' do + allow(ValidEmail2::Address).to receive(:new).with(email).and_return(invalid_email_address) + expect { service.perform }.to raise_error do |error| + expect(error).to be_a(CustomExceptions::Account::InvalidEmail) + expect(error.message).to eq(I18n.t('errors.signup.invalid_email')) + end + end + end + + context 'when domain is blocked' do + let(:email) { 'test@gmail.com' } + + it 'raises InvalidEmail with blocked domain message' do + allow(ValidEmail2::Address).to receive(:new).with(email).and_return(valid_email_address) + expect { service.perform }.to raise_error do |error| + expect(error).to be_a(CustomExceptions::Account::InvalidEmail) + expect(error.message).to eq(I18n.t('errors.signup.blocked_domain')) + end + end + end + + context 'when domain is blocked (case insensitive)' do + let(:email) { 'test@GMAIL.COM' } + + it 'raises InvalidEmail with blocked domain message' do + allow(ValidEmail2::Address).to receive(:new).with(email).and_return(valid_email_address) + expect { service.perform }.to raise_error do |error| + expect(error).to be_a(CustomExceptions::Account::InvalidEmail) + expect(error.message).to eq(I18n.t('errors.signup.blocked_domain')) + end + end + end + + context 'when email is from disposable provider' do + let(:email) { 'test@mailinator.com' } + + it 'raises InvalidEmail with disposable message' do + allow(ValidEmail2::Address).to receive(:new).with(email).and_return(disposable_email_address) + expect { service.perform }.to raise_error do |error| + expect(error).to be_a(CustomExceptions::Account::InvalidEmail) + expect(error.message).to eq(I18n.t('errors.signup.disposable_email')) + end + end + end + + context 'when email is valid business email' do + let(:email) { 'test@example.com' } + + it 'returns true' do + allow(ValidEmail2::Address).to receive(:new).with(email).and_return(valid_email_address) + expect(service.perform).to be(true) + end + end + end +end diff --git a/spec/services/account_deletion_service_spec.rb b/spec/services/account_deletion_service_spec.rb new file mode 100644 index 000000000..6b263c5b3 --- /dev/null +++ b/spec/services/account_deletion_service_spec.rb @@ -0,0 +1,63 @@ +require 'rails_helper' + +RSpec.describe AccountDeletionService do + let(:account) { create(:account) } + let(:mailer) { instance_double(ActionMailer::MessageDelivery, deliver_later: nil) } + + describe '#perform' do + before do + allow(DeleteObjectJob).to receive(:perform_later) + allow(AdministratorNotifications::AccountComplianceMailer).to receive(:with).and_return( + instance_double(AdministratorNotifications::AccountComplianceMailer, account_deleted: mailer) + ) + end + + it 'enqueues DeleteObjectJob with the account' do + described_class.new(account: account).perform + + expect(DeleteObjectJob).to have_received(:perform_later).with(account) + end + + it 'sends a compliance notification email' do + described_class.new(account: account).perform + + expect(AdministratorNotifications::AccountComplianceMailer).to have_received(:with) do |args| + expect(args[:account]).to eq(account) + expect(args).to include(:soft_deleted_users) + end + expect(mailer).to have_received(:deliver_later) + end + + context 'when handling users' do + let(:user_with_one_account) { create(:user) } + let(:user_with_multiple_accounts) { create(:user) } + let(:second_account) { create(:account) } + + before do + create(:account_user, user: user_with_one_account, account: account) + create(:account_user, user: user_with_multiple_accounts, account: account) + create(:account_user, user: user_with_multiple_accounts, account: second_account) + end + + it 'soft deletes users who only belong to the deleted account' do + original_email = user_with_one_account.email + + described_class.new(account: account).perform + + # Reload the user to get the updated email + user_with_one_account.reload + expect(user_with_one_account.email).to eq("#{original_email}-deleted.com") + end + + it 'does not modify emails for users belonging to multiple accounts' do + original_email = user_with_multiple_accounts.email + + described_class.new(account: account).perform + + # Reload the user to get the updated email + user_with_multiple_accounts.reload + expect(user_with_multiple_accounts.email).to eq(original_email) + end + end + end +end diff --git a/spec/services/conversations/message_window_service_spec.rb b/spec/services/conversations/message_window_service_spec.rb index 34c5ebbad..32542e87e 100644 --- a/spec/services/conversations/message_window_service_spec.rb +++ b/spec/services/conversations/message_window_service_spec.rb @@ -56,6 +56,7 @@ RSpec.describe Conversations::MessageWindowService do describe 'on Facebook channels' do before do stub_request(:post, /graph.facebook.com/) + GlobalConfig.clear_cache end let!(:facebook_channel) { create(:channel_facebook_page) } diff --git a/spec/services/crm/leadsquared/mappers/contact_mapper_spec.rb b/spec/services/crm/leadsquared/mappers/contact_mapper_spec.rb index 4e7c06fdb..b6bf51f93 100644 --- a/spec/services/crm/leadsquared/mappers/contact_mapper_spec.rb +++ b/spec/services/crm/leadsquared/mappers/contact_mapper_spec.rb @@ -16,6 +16,7 @@ RSpec.describe Crm::Leadsquared::Mappers::ContactMapper do name: 'John', last_name: 'Doe', email: 'john@example.com', + # the phone number is intentionally wrong phone_number: '+1234567890' ) @@ -29,6 +30,19 @@ RSpec.describe Crm::Leadsquared::Mappers::ContactMapper do 'Source' => 'Test Brand' ) end + + it 'represents the phone number correctly' do + contact.update!( + name: 'John', + last_name: 'Doe', + email: 'john@example.com', + phone_number: '+917507684392' + ) + + mapped_data = described_class.map(contact) + + expect(mapped_data).to include('Mobile' => '+91-7507684392') + end end end end diff --git a/spec/services/crm/leadsquared/mappers/conversation_mapper_spec.rb b/spec/services/crm/leadsquared/mappers/conversation_mapper_spec.rb index 29f7136f1..0ddd4ac9f 100644 --- a/spec/services/crm/leadsquared/mappers/conversation_mapper_spec.rb +++ b/spec/services/crm/leadsquared/mappers/conversation_mapper_spec.rb @@ -6,15 +6,39 @@ RSpec.describe Crm::Leadsquared::Mappers::ConversationMapper do let(:conversation) { create(:conversation, account: account, inbox: inbox) } let(:user) { create(:user, name: 'John Doe') } let(:contact) { create(:contact, name: 'Jane Smith') } + let(:hook) do + create(:integrations_hook, :leadsquared, account: account, settings: { + 'access_key' => 'test_access_key', + 'secret_key' => 'test_secret_key', + 'endpoint_url' => 'https://api.leadsquared.com/v2', + 'timezone' => 'UTC' + }) + end + let(:hook_with_pst) do + create(:integrations_hook, :leadsquared, account: account, settings: { + 'access_key' => 'test_access_key', + 'secret_key' => 'test_secret_key', + 'endpoint_url' => 'https://api.leadsquared.com/v2', + 'timezone' => 'America/Los_Angeles' + }) + end + let(:hook_without_timezone) do + create(:integrations_hook, :leadsquared, account: account, settings: { + 'access_key' => 'test_access_key', + 'secret_key' => 'test_secret_key', + 'endpoint_url' => 'https://api.leadsquared.com/v2' + }) + end before do + account.enable_features('crm_integration') allow(GlobalConfig).to receive(:get).with('BRAND_NAME').and_return({ 'BRAND_NAME' => 'TestBrand' }) end describe '.map_conversation_activity' do - it 'generates conversation activity note' do - travel_to(Time.zone.parse('2024-01-01 10:00:00')) do - result = described_class.map_conversation_activity(conversation) + it 'generates conversation activity note with UTC timezone' do + travel_to(Time.zone.parse('2024-01-01 10:00:00 UTC')) do + result = described_class.map_conversation_activity(hook, conversation) expect(result).to include('New conversation started on TestBrand') expect(result).to include('Channel: Test Inbox') @@ -23,12 +47,29 @@ RSpec.describe Crm::Leadsquared::Mappers::ConversationMapper do expect(result).to include('View in TestBrand: http://') end end + + it 'formats time according to hook timezone setting' do + travel_to(Time.zone.parse('2024-01-01 18:00:00 UTC')) do + result = described_class.map_conversation_activity(hook_with_pst, conversation) + + # PST is UTC-8, so 18:00 UTC becomes 10:00:00 PST + expect(result).to include('Created: 2024-01-01 10:00:00') + end + end + + it 'falls back to system timezone when hook has no timezone setting' do + travel_to(Time.zone.parse('2024-01-01 10:00:00')) do + result = described_class.map_conversation_activity(hook_without_timezone, conversation) + + expect(result).to include('Created: 2024-01-01 10:00:00') + end + end end describe '.map_transcript_activity' do context 'when conversation has no messages' do it 'returns no messages message' do - result = described_class.map_transcript_activity(conversation) + result = described_class.map_transcript_activity(hook, conversation) expect(result).to eq('No messages in conversation') end end @@ -68,7 +109,7 @@ RSpec.describe Crm::Leadsquared::Mappers::ConversationMapper do end it 'generates transcript with messages in reverse chronological order' do - result = described_class.map_transcript_activity(conversation) + result = described_class.map_transcript_activity(hook, conversation) expect(result).to include('Conversation Transcript from TestBrand') expect(result).to include('Channel: Test Inbox') @@ -83,6 +124,22 @@ RSpec.describe Crm::Leadsquared::Mappers::ConversationMapper do expect(message_positions['[2024-01-01 10:01] Jane Smith: Hi there']).to be < message_positions['[2024-01-01 10:00] John Doe: Hello'] end + it 'formats message times according to hook timezone setting' do + travel_to(Time.zone.parse('2024-01-01 18:00:00 UTC')) do + create(:message, + conversation: conversation, + sender: user, + content: 'Test message', + message_type: :outgoing, + created_at: Time.zone.parse('2024-01-01 18:00:00 UTC')) + + result = described_class.map_transcript_activity(hook_with_pst, conversation) + + # PST is UTC-8, so 18:00 UTC becomes 10:00 PST + expect(result).to include('[2024-01-01 10:00] John Doe: Test message') + end + end + context 'when message has attachments' do let(:message_with_attachment) do create(:message, :with_attachment, @@ -96,7 +153,7 @@ RSpec.describe Crm::Leadsquared::Mappers::ConversationMapper do before { message_with_attachment } it 'includes attachment information' do - result = described_class.map_transcript_activity(conversation) + result = described_class.map_transcript_activity(hook, conversation) expect(result).to include('See attachment') expect(result).to include('[Attachment: image]') @@ -116,7 +173,7 @@ RSpec.describe Crm::Leadsquared::Mappers::ConversationMapper do before { empty_message } it 'shows no content placeholder' do - result = described_class.map_transcript_activity(conversation) + result = described_class.map_transcript_activity(hook, conversation) expect(result).to include('[No content]') end end @@ -134,25 +191,12 @@ RSpec.describe Crm::Leadsquared::Mappers::ConversationMapper do before { unnamed_sender_message } it 'uses sender type and id' do - result = described_class.map_transcript_activity(conversation) + result = described_class.map_transcript_activity(hook, conversation) expect(result).to include("User #{unnamed_sender_message.sender_id}") end end end - context 'when specific messages are provided' do - let(:message1) { create(:message, conversation: conversation, content: 'Message 1', message_type: :outgoing) } - let(:message2) { create(:message, conversation: conversation, content: 'Message 2', message_type: :outgoing) } - let(:specific_messages) { [message1] } - - it 'only includes provided messages' do - result = described_class.map_transcript_activity(conversation, specific_messages) - - expect(result).to include('Message 1') - expect(result).not_to include('Message 2') - end - end - context 'when messages exceed the ACTIVITY_NOTE_MAX_SIZE' do it 'truncates messages to stay within the character limit' do # Create a large number of messages with reasonably sized content @@ -169,7 +213,7 @@ RSpec.describe Crm::Leadsquared::Mappers::ConversationMapper do created_at: Time.zone.parse("2024-01-01 #{10 + i}:00:00")) end - result = described_class.map_transcript_activity(conversation, messages) + result = described_class.map_transcript_activity(hook, conversation) # Verify latest message is included (message 14) expect(result).to include("[2024-01-02 00:00] John Doe: #{long_message_content} 14") @@ -183,19 +227,19 @@ RSpec.describe Crm::Leadsquared::Mappers::ConversationMapper do expect(result.length).to be <= described_class::ACTIVITY_NOTE_MAX_SIZE + 100 # Verify that not all messages are included (some were truncated) - expect(messages.count).to be > result.scan(/John Doe:/).count + expect(messages.count).to be > result.scan('John Doe:').count end it 'respects the ACTIVITY_NOTE_MAX_SIZE constant' do # Create a single message that would exceed the limit by itself giant_content = 'A' * 2000 - message = create(:message, - conversation: conversation, - sender: user, - content: giant_content, - message_type: :outgoing) + create(:message, + conversation: conversation, + sender: user, + content: giant_content, + message_type: :outgoing) - result = described_class.map_transcript_activity(conversation, [message]) + result = described_class.map_transcript_activity(hook, conversation) # Extract just the formatted messages part id = conversation.display_id diff --git a/spec/services/crm/leadsquared/processor_service_spec.rb b/spec/services/crm/leadsquared/processor_service_spec.rb index efdead00b..7008eb064 100644 --- a/spec/services/crm/leadsquared/processor_service_spec.rb +++ b/spec/services/crm/leadsquared/processor_service_spec.rb @@ -116,7 +116,7 @@ RSpec.describe Crm::Leadsquared::ProcessorService do before do allow(Crm::Leadsquared::Mappers::ConversationMapper).to receive(:map_conversation_activity) - .with(conversation) + .with(hook, conversation) .and_return(activity_note) end @@ -180,7 +180,7 @@ RSpec.describe Crm::Leadsquared::ProcessorService do before do allow(Crm::Leadsquared::Mappers::ConversationMapper).to receive(:map_transcript_activity) - .with(conversation) + .with(hook, conversation) .and_return(activity_note) end diff --git a/spec/services/crm/leadsquared/setup_service_spec.rb b/spec/services/crm/leadsquared/setup_service_spec.rb index 1d907ecda..8ebdc9691 100644 --- a/spec/services/crm/leadsquared/setup_service_spec.rb +++ b/spec/services/crm/leadsquared/setup_service_spec.rb @@ -8,6 +8,7 @@ RSpec.describe Crm::Leadsquared::SetupService do let(:activity_client) { instance_double(Crm::Leadsquared::Api::ActivityClient) } let(:endpoint_response) do { + 'TimeZone' => 'Asia/Kolkata', 'LSQCommonServiceURLs' => { 'api' => 'api-in.leadsquared.com', 'app' => 'app.leadsquared.com' @@ -45,6 +46,7 @@ RSpec.describe Crm::Leadsquared::SetupService do updated_settings = hook.reload.settings expect(updated_settings['endpoint_url']).to eq('https://api-in.leadsquared.com/v2/') expect(updated_settings['app_url']).to eq('https://app.leadsquared.com/') + expect(updated_settings['timezone']).to eq('Asia/Kolkata') expect(updated_settings['conversation_activity_code']).to eq(1001) expect(updated_settings['transcript_activity_code']).to eq(1002) end @@ -71,6 +73,7 @@ RSpec.describe Crm::Leadsquared::SetupService do updated_settings = hook.reload.settings expect(updated_settings['endpoint_url']).to eq('https://api-in.leadsquared.com/v2/') expect(updated_settings['app_url']).to eq('https://app.leadsquared.com/') + expect(updated_settings['timezone']).to eq('Asia/Kolkata') expect(updated_settings['conversation_activity_code']).to eq(1001) expect(updated_settings['transcript_activity_code']).to eq(1002) end diff --git a/spec/services/message_templates/hook_execution_service_spec.rb b/spec/services/message_templates/hook_execution_service_spec.rb index 9d9d82922..6e97aac42 100644 --- a/spec/services/message_templates/hook_execution_service_spec.rb +++ b/spec/services/message_templates/hook_execution_service_spec.rb @@ -121,8 +121,9 @@ describe MessageTemplates::HookExecutionService do create(:message, conversation: conversation, message_type: 'incoming') end - it 'calls ::MessageTemplates::Template::CsatSurvey when a conversation is resolved in an inbox with survey enabled' do + it 'calls ::MessageTemplates::Template::CsatSurvey when a conversation is resolved in an inbox with survey enabled and can reply' do conversation.inbox.update(csat_survey_enabled: true) + allow(conversation).to receive(:can_reply?).and_return(true) conversation.resolved! Conversations::ActivityMessageJob.perform_now(conversation, @@ -172,6 +173,32 @@ describe MessageTemplates::HookExecutionService do expect(MessageTemplates::Template::CsatSurvey).not_to have_received(:new).with(conversation: conversation) expect(csat_survey).not_to have_received(:perform) end + + it 'will not call ::MessageTemplates::Template::CsatSurvey if cannot reply' do + conversation.inbox.update(csat_survey_enabled: true) + allow(conversation).to receive(:can_reply?).and_return(false) + + conversation.resolved! + Conversations::ActivityMessageJob.perform_now(conversation, + { account_id: conversation.account_id, inbox_id: conversation.inbox_id, message_type: :activity, + content: 'Conversation marked resolved!!' }) + + expect(MessageTemplates::Template::CsatSurvey).not_to have_received(:new).with(conversation: conversation) + expect(csat_survey).not_to have_received(:perform) + end + + it 'creates activity message when CSAT not sent due to messaging window restriction' do + conversation.inbox.update(csat_survey_enabled: true) + allow(conversation).to receive(:can_reply?).and_return(false) + allow(conversation).to receive(:create_csat_not_sent_activity_message) + + conversation.resolved! + Conversations::ActivityMessageJob.perform_now(conversation, + { account_id: conversation.account_id, inbox_id: conversation.inbox_id, message_type: :activity, + content: 'Conversation marked resolved!!' }) + + expect(conversation).to have_received(:create_csat_not_sent_activity_message) + end end context 'when it is after working hours' do @@ -194,23 +221,44 @@ describe MessageTemplates::HookExecutionService do expect(out_of_office_service).to have_received(:perform) end - it 'does not call ::MessageTemplates::Template::OutOfOffice when there are recent outgoing messages' do - contact = create(:contact) - conversation = create(:conversation, contact: contact) + context 'with recent outgoing messages' do + it 'does not call ::MessageTemplates::Template::OutOfOffice when there are recent outgoing messages' do + contact = create(:contact) + conversation = create(:conversation, contact: contact) - conversation.inbox.update(working_hours_enabled: true, out_of_office_message: 'We are out of office') - conversation.inbox.working_hours.today.update!(closed_all_day: true) + conversation.inbox.update(working_hours_enabled: true, out_of_office_message: 'We are out of office') + conversation.inbox.working_hours.today.update!(closed_all_day: true) - create(:message, conversation: conversation, message_type: :outgoing, created_at: 2.minutes.ago) + create(:message, conversation: conversation, message_type: :outgoing, created_at: 2.minutes.ago) - out_of_office_service = double - allow(MessageTemplates::Template::OutOfOffice).to receive(:new).and_return(out_of_office_service) - allow(out_of_office_service).to receive(:perform).and_return(true) + out_of_office_service = double + allow(MessageTemplates::Template::OutOfOffice).to receive(:new).and_return(out_of_office_service) + allow(out_of_office_service).to receive(:perform).and_return(true) - create(:message, conversation: conversation) + create(:message, conversation: conversation) - expect(MessageTemplates::Template::OutOfOffice).not_to have_received(:new) - expect(out_of_office_service).not_to have_received(:perform) + expect(MessageTemplates::Template::OutOfOffice).not_to have_received(:new) + expect(out_of_office_service).not_to have_received(:perform) + end + + it 'ignores private note and calls ::MessageTemplates::Template::OutOfOffice' do + contact = create(:contact) + conversation = create(:conversation, contact: contact) + + conversation.inbox.update(working_hours_enabled: true, out_of_office_message: 'We are out of office') + conversation.inbox.working_hours.today.update!(closed_all_day: true) + + create(:message, conversation: conversation, private: true, message_type: :outgoing, created_at: 2.minutes.ago) + + out_of_office_service = double + allow(MessageTemplates::Template::OutOfOffice).to receive(:new).and_return(out_of_office_service) + allow(out_of_office_service).to receive(:perform).and_return(true) + + create(:message, conversation: conversation) + + expect(MessageTemplates::Template::OutOfOffice).to have_received(:new).with(conversation: conversation) + expect(out_of_office_service).to have_received(:perform) + end end it 'will not calls ::MessageTemplates::Template::OutOfOffice when outgoing message' do diff --git a/spec/services/search_service_spec.rb b/spec/services/search_service_spec.rb index af097a2c9..22809d042 100644 --- a/spec/services/search_service_spec.rb +++ b/spec/services/search_service_spec.rb @@ -10,6 +10,11 @@ describe SearchService do let!(:harry) { create(:contact, name: 'Harry Potter', email: 'test@test.com', account_id: account.id) } let!(:conversation) { create(:conversation, contact: harry, inbox: inbox, account: account) } let!(:message) { create(:message, account: account, inbox: inbox, content: 'Harry Potter is a wizard') } + let!(:portal) { create(:portal, account: account) } + let(:article) do + create(:article, title: 'Harry Potter Magic Guide', content: 'Learn about wizardry', account: account, portal: portal, author: user, + status: 'published') + end before do create(:inbox_member, user: user, inbox: inbox) @@ -27,7 +32,7 @@ describe SearchService do it 'returns all for all' do search_type = 'all' search = described_class.new(current_user: user, current_account: account, params: params, search_type: search_type) - expect(search.perform.keys).to match_array(%i[contacts messages conversations]) + expect(search.perform.keys).to match_array(%i[contacts messages conversations articles]) end it 'returns contacts for contacts' do @@ -47,6 +52,12 @@ describe SearchService do search = described_class.new(current_user: user, current_account: account, params: params, search_type: search_type) expect(search.perform.keys).to match_array(%i[conversations]) end + + it 'returns articles for articles' do + search_type = 'Article' + search = described_class.new(current_user: user, current_account: account, params: params, search_type: search_type) + expect(search.perform.keys).to match_array(%i[articles]) + end end context 'when contact search' do @@ -143,6 +154,50 @@ describe SearchService do expect(search.perform[:conversations].map(&:id)).to include new_converstion.id end end + + context 'when article search' do + it 'orders results by updated_at desc' do + # Create articles with explicit timestamps + older_time = 2.days.ago + newer_time = 1.hour.ago + + article2 = create(:article, title: 'Spellcasting Guide', + account: account, portal: portal, author: user, status: 'published') + # rubocop:disable Rails/SkipsModelValidations + article2.update_column(:updated_at, older_time) + # rubocop:enable Rails/SkipsModelValidations + + article3 = create(:article, title: 'Spellcasting Manual', + account: account, portal: portal, author: user, status: 'published') + # rubocop:disable Rails/SkipsModelValidations + article3.update_column(:updated_at, newer_time) + # rubocop:enable Rails/SkipsModelValidations + + params = { q: 'Spellcasting' } + search = described_class.new(current_user: user, current_account: account, params: params, search_type: 'Article') + results = search.perform[:articles] + + # Check the timestamps to understand ordering + results.map { |a| [a.id, a.updated_at] } + + # Should be ordered by updated_at desc (newer first) + expect(results.length).to eq(2) + expect(results.first.updated_at).to be > results.second.updated_at + end + + it 'returns paginated results' do + # Create many articles to test pagination + 16.times do |i| + create(:article, title: "Magic Article #{i}", account: account, portal: portal, author: user, status: 'published') + end + + params = { q: 'Magic', page: 1 } + search = described_class.new(current_user: user, current_account: account, params: params, search_type: 'Article') + results = search.perform[:articles] + + expect(results.length).to eq(15) # Default per_page is 15 + end + end end describe '#use_gin_search' do diff --git a/swagger/definitions/error/bad_request.yml b/swagger/definitions/error/bad_request.yml index b6facb361..f103edcb5 100644 --- a/swagger/definitions/error/bad_request.yml +++ b/swagger/definitions/error/bad_request.yml @@ -6,4 +6,4 @@ properties: errors: type: array items: - $ref: '#/definitions/request_error' + $ref: '#/components/schemas/request_error' diff --git a/swagger/definitions/index.yml b/swagger/definitions/index.yml index d11ec7988..57e10e2dd 100644 --- a/swagger/definitions/index.yml +++ b/swagger/definitions/index.yml @@ -1,13 +1,11 @@ -## ---------- ERRORS ------------- ## +## ---------- ERRORS -------------- ## ## -------------------------------- ## bad_request_error: $ref: ./error/bad_request.yml request_error: $ref: ./error/request.yml - - -## ---------- RESOURCE ------------- ## +## ---------- RESOURCE ------------ ## ## -------------------------------- ## generic_id: @@ -18,12 +16,20 @@ custom_attribute: $ref: ./resource/custom_attribute.yml automation_rule: $ref: ./resource/automation_rule.yml +automation_rule_item: + $ref: ./resource/automation_rule_item.yml portal: $ref: ./resource/portal.yml -category: - $ref: ./resource/category.yml -article: - $ref: ./resource/article.yml +portal_single: + $ref: ./resource/portal_single.yml +portal_config: + $ref: ./resource/portal_config.yml +portal_logo: + $ref: ./resource/portal_logo.yml +portal_meta: + $ref: ./resource/portal_meta.yml +portal_item: + $ref: ./resource/portal_item.yml category: $ref: ./resource/category.yml article: @@ -40,6 +46,8 @@ agent: $ref: ./resource/agent.yml inbox: $ref: ./resource/inbox.yml +inbox_contact: + $ref: ./resource/inbox_contact.yml agent_bot: $ref: ./resource/agent_bot.yml contact_inboxes: @@ -52,6 +60,8 @@ webhook: $ref: ./resource/webhook.yml account: $ref: ./resource/account.yml +account_user: + $ref: ./resource/account_user.yml platform_account: $ref: ./resource/platform_account.yml team: @@ -71,13 +81,18 @@ public_message: public_inbox: $ref: ./resource/public/inbox.yml - -## ---------- REQUEST------------- ## +## ---------- REQUEST ------------- ## ## -------------------------------- ## account_create_update_payload: $ref: ./request/account/create_update_payload.yml +account_user_create_update_payload: + $ref: ./request/account_user/create_update_payload.yml + +platform_agent_bot_create_update_payload: + $ref: ./request/platform/agent_bot/create_update_payload.yml + agent_bot_create_update_payload: $ref: ./request/agent_bot/create_update_payload.yml @@ -89,21 +104,36 @@ canned_response_create_update_payload: custom_attribute_create_update_payload: $ref: ./request/custom_attribute/create_update_payload.yml -## contact -contact_create: - $ref: ./request/contact/create.yml -contact_update: - $ref: ./request/contact/update.yml -## conversation -conversation_message_create: - $ref: ./request/conversation/create_message.yml +## Agent +agent_create_payload: + $ref: ./request/agent/create_payload.yml +agent_update_payload: + $ref: ./request/agent/update_payload.yml -# Team request Payload +## Contact +contact_create_payload: + $ref: ./request/contact/create_payload.yml +contact_update_payload: + $ref: ./request/contact/update_payload.yml + +## Conversation +conversation_create_payload: + $ref: ./request/conversation/create_payload.yml +conversation_message_create_payload: + $ref: ./request/conversation/create_message_payload.yml + +# Inbox +inbox_create_payload: + $ref: ./request/inbox/create_payload.yml +inbox_update_payload: + $ref: ./request/inbox/update_payload.yml + +# Team team_create_update_payload: $ref: ./request/team/create_update_payload.yml -# Custom Filter request Payload +# Custom Filter custom_filter_create_update_payload: $ref: ./request/custom_filter/create_update_payload.yml @@ -128,7 +158,6 @@ category_create_update_payload: article_create_update_payload: $ref: ./request/portal/article_create_update_payload.yml - ## public requests public_contact_create_update_payload: $ref: ./request/public/contact/create_update_payload.yml @@ -141,37 +170,36 @@ public_message_update_payload: public_conversation_create_payload: $ref: ./request/public/conversation/create_payload.yml -## ---------- RESPONSE ------------- ## +## ---------- RESPONSE ------------ ## ## -------------------------------- ## -## contact +## Contact extended_contact: allOf: - - $ref: '#/definitions/contact' + - $ref: '#/components/schemas/contact' - $ref: ./resource/extension/contact/show.yml contact_base: allOf: - - $ref: '#/definitions/generic_id' - - $ref: '#/definitions/contact' + - $ref: '#/components/schemas/generic_id' + - $ref: '#/components/schemas/contact' contact_list: type: array description: 'array of contacts' items: allOf: - - $ref: '#/definitions/generic_id' - - $ref: '#/definitions/contact' + - $ref: '#/components/schemas/contact' contact_conversations: type: array description: 'array of conversations' items: allOf: - - $ref: '#/definitions/conversation' + - $ref: '#/components/schemas/conversation' - $ref: ./resource/extension/contact/conversation.yml - $ref: ./resource/extension/conversation/with_display_id.yml contact_labels: $ref: ./resource/extension/contact/labels.yml -## conversation +## Conversation conversation_list: $ref: ./resource/extension/conversation/list.yml conversation_show: @@ -181,9 +209,33 @@ conversation_status_toggle: conversation_labels: $ref: ./resource/extension/conversation/labels.yml - -## report +## Report account_summary: $ref: './resource/reports/summary.yml' agent_conversation_metrics: $ref: './resource/reports/conversation/agent.yml' + +contact_detail: + $ref: ./resource/contact_detail.yml +message_detailed: + $ref: ./resource/message_detailed.yml +conversation_meta: + $ref: ./resource/conversation_meta.yml +conversation_messages: + $ref: ./resource/conversation_messages.yml +contact_meta: + $ref: ./resource/contact_meta.yml +contact_inbox: + $ref: ./resource/contact_inbox.yml +contact_list_item: + $ref: ./resource/contact_list_item.yml +contacts_list_response: + $ref: ./resource/contacts_list_response.yml +contact_show_response: + $ref: ./resource/contact_show_response.yml +contact_conversation_message: + $ref: ./resource/contact_conversation_message.yml +contact_conversations_response: + $ref: ./resource/contact_conversations_response.yml +contactable_inboxes_response: + $ref: ./resource/contactable_inboxes_response.yml diff --git a/swagger/definitions/request/account/create_update_payload.yml b/swagger/definitions/request/account/create_update_payload.yml index 63c0eed13..b11b82373 100644 --- a/swagger/definitions/request/account/create_update_payload.yml +++ b/swagger/definitions/request/account/create_update_payload.yml @@ -3,3 +3,29 @@ properties: name: type: string description: Name of the account + example: 'My Account' + locale: + type: string + description: The locale of the account + example: 'en' + domain: + type: string + description: The domain of the account + example: 'example.com' + support_email: + type: string + description: The support email of the account + example: 'support@example.com' + status: + type: string + enum: ['active', 'suspended'] + description: The status of the account + example: 'active' + limits: + type: object + description: The limits of the account + example: {} + custom_attributes: + type: object + description: The custom attributes of the account + example: {} diff --git a/swagger/definitions/request/account_user/create_update_payload.yml b/swagger/definitions/request/account_user/create_update_payload.yml new file mode 100644 index 000000000..d2982c4cf --- /dev/null +++ b/swagger/definitions/request/account_user/create_update_payload.yml @@ -0,0 +1,13 @@ +type: object +required: + - user_id + - role +properties: + user_id: + type: integer + description: The ID of the user + example: 1 + role: + type: string + description: whether user is an administrator or agent + example: administrator diff --git a/swagger/definitions/request/agent/create_payload.yml b/swagger/definitions/request/agent/create_payload.yml new file mode 100644 index 000000000..1daeae83a --- /dev/null +++ b/swagger/definitions/request/agent/create_payload.yml @@ -0,0 +1,28 @@ +type: object +required: + - name + - email + - role +properties: + name: + type: string + description: Full Name of the agent + example: 'John Doe' + email: + type: string + description: Email of the Agent + example: 'john.doe@acme.inc' + role: + type: string + enum: ['agent', 'administrator'] + description: Whether its administrator or agent + example: 'agent' + availability_status: + type: string + enum: ['available', 'busy', 'offline'] + description: The availability setting of the agent. + example: 'available' + auto_offline: + type: boolean + description: Whether the availability status of agent is configured to go offline automatically when away. + example: true diff --git a/swagger/definitions/request/agent/update_payload.yml b/swagger/definitions/request/agent/update_payload.yml new file mode 100644 index 000000000..fc8d1457d --- /dev/null +++ b/swagger/definitions/request/agent/update_payload.yml @@ -0,0 +1,18 @@ +type: object +required: + - role +properties: + role: + type: string + enum: ['agent', 'administrator'] + description: Whether its administrator or agent + example: 'agent' + availability_status: + type: string + enum: ['available', 'busy', 'offline'] + description: The availability status of the agent. + example: 'available' + auto_offline: + type: boolean + description: Whether the availability status of agent is configured to go offline automatically when away. + example: true diff --git a/swagger/definitions/request/agent_bot/create_update_payload.yml b/swagger/definitions/request/agent_bot/create_update_payload.yml index 4dc74bea3..4a95b42bf 100644 --- a/swagger/definitions/request/agent_bot/create_update_payload.yml +++ b/swagger/definitions/request/agent_bot/create_update_payload.yml @@ -3,9 +3,28 @@ properties: name: type: string description: The name of the agent bot + example: 'My Agent Bot' description: type: string - description: The description about the agent bot + description: The description of the agent bot + example: 'This is a sample agent bot' outgoing_url: type: string description: The webhook URL for the bot + example: 'https://example.com/webhook' + avatar: + type: string + format: binary + description: Send the form data with the avatar image binary or use the avatar_url + avatar_url: + type: string + description: The url to a jpeg, png file for the agent bot avatar + example: https://example.com/avatar.png + bot_type: + type: integer + description: The type of the bot (0 for webhook) + example: 0 + bot_config: + type: object + description: The configuration for the bot + example: {} diff --git a/swagger/definitions/request/automation_rule/create_update_payload.yml b/swagger/definitions/request/automation_rule/create_update_payload.yml index 20d362e48..091fa2aaa 100644 --- a/swagger/definitions/request/automation_rule/create_update_payload.yml +++ b/swagger/definitions/request/automation_rule/create_update_payload.yml @@ -36,6 +36,6 @@ properties: example: attribute_key: content filter_operator: contains - query_operator: nil + query_operator: OR values: - help diff --git a/swagger/definitions/request/canned_response/create_update_payload.yml b/swagger/definitions/request/canned_response/create_update_payload.yml index ef6f7f74a..3673b4798 100644 --- a/swagger/definitions/request/canned_response/create_update_payload.yml +++ b/swagger/definitions/request/canned_response/create_update_payload.yml @@ -3,6 +3,8 @@ properties: content: type: string description: Message content for canned response + example: 'Hello, {{contact.name}}! Welcome to our service.' short_code: type: string description: Short Code for quick access of the canned response + example: 'welcome' diff --git a/swagger/definitions/request/contact/create_payload.yml b/swagger/definitions/request/contact/create_payload.yml new file mode 100644 index 000000000..7f74e501c --- /dev/null +++ b/swagger/definitions/request/contact/create_payload.yml @@ -0,0 +1,44 @@ +type: object +required: + - inbox_id +properties: + inbox_id: + type: number + description: ID of the inbox to which the contact belongs + example: 1 + name: + type: string + description: name of the contact + example: Alice + email: + type: string + description: email of the contact + example: alice@acme.inc + blocked: + type: boolean + description: whether the contact is blocked or not + example: false + phone_number: + type: string + description: phone number of the contact + example: '+123456789' + avatar: + type: string + format: binary + description: Send the form data with the avatar image binary or use the avatar_url + avatar_url: + type: string + description: The url to a jpeg, png file for the contact avatar + example: https://example.com/avatar.png + identifier: + type: string + description: A unique identifier for the contact in external system + example: '1234567890' + additional_attributes: + type: object + description: An object where you can store additional attributes for contact. example {"type":"customer", "age":30} + example: { 'type': 'customer', 'age': 30 } + custom_attributes: + type: object + description: An object where you can store custom attributes for contact. example {"type":"customer", "age":30}, this should have a valid custom attribute definition. + example: {} diff --git a/swagger/definitions/request/contact/update.yml b/swagger/definitions/request/contact/update.yml deleted file mode 100644 index 4a12672a3..000000000 --- a/swagger/definitions/request/contact/update.yml +++ /dev/null @@ -1,25 +0,0 @@ -type: object -properties: - name: - type: string - description: name of the contact - email: - type: string - description: email of the contact - phone_number: - type: string - description: phone number of the contact - avatar: - type: string - format: binary - description: Send the form data with the avatar image binary or use the avatar_url - avatar_url: - type: string - description: The url to a jpeg, png file for the contact avatar - identifier: - type: string - description: A unique identifier for the contact in external system - custom_attributes: - type: object - description: An object where you can store custom attributes for contact. example {"type":"customer", "age":30} - diff --git a/swagger/definitions/request/contact/create.yml b/swagger/definitions/request/contact/update_payload.yml similarity index 53% rename from swagger/definitions/request/contact/create.yml rename to swagger/definitions/request/contact/update_payload.yml index 5acbe5f94..99bec3548 100644 --- a/swagger/definitions/request/contact/create.yml +++ b/swagger/definitions/request/contact/update_payload.yml @@ -1,18 +1,21 @@ type: object -required: - - inbox_id properties: - inbox_id: - type: number name: type: string description: name of the contact + example: Alice email: type: string description: email of the contact + example: alice@acme.inc + blocked: + type: boolean + description: whether the contact is blocked or not + example: false phone_number: type: string description: phone number of the contact + example: '+123456789' avatar: type: string format: binary @@ -20,9 +23,16 @@ properties: avatar_url: type: string description: The url to a jpeg, png file for the contact avatar + example: https://example.com/avatar.png identifier: type: string description: A unique identifier for the contact in external system + example: '1234567890' + additional_attributes: + type: object + description: An object where you can store additional attributes for contact. example {"type":"customer", "age":30} + example: { 'type': 'customer', 'age': 30 } custom_attributes: type: object - description: An object where you can store custom attributes for contact. example {"type":"customer", "age":30} + description: An object where you can store custom attributes for contact. example {"type":"customer", "age":30}, this should have a valid custom attribute definition. + example: {} diff --git a/swagger/definitions/request/conversation/create_message.yml b/swagger/definitions/request/conversation/create_message_payload.yml similarity index 68% rename from swagger/definitions/request/conversation/create_message.yml rename to swagger/definitions/request/conversation/create_message_payload.yml index b3c9b935b..ef1a051c2 100644 --- a/swagger/definitions/request/conversation/create_message.yml +++ b/swagger/definitions/request/conversation/create_message_payload.yml @@ -5,20 +5,29 @@ properties: content: type: string description: The content of the message + example: 'Hello, how can I help you?' message_type: type: string enum: ['outgoing', 'incoming'] + description: The type of the message + example: 'outgoing' private: type: boolean description: Flag to identify if it is a private note + example: false content_type: type: string - enum: ['text', 'input_email', 'cards', 'input_select', 'form' , 'article'] - example: 'cards' - description: 'if you want to create custom message types' + enum: ['text', 'input_email', 'cards', 'input_select', 'form', 'article'] + description: Content type of the message + example: 'text' content_attributes: type: object - description: attributes based on your content type + description: Attributes based on the content type + example: {} + campaign_id: + type: integer + description: The campaign id to which the message belongs + example: 1 template_params: type: object description: The template params for the message in case of whatsapp Channel @@ -35,8 +44,8 @@ properties: type: string description: Language of the template example: en_US - processed_params: + processed_params: type: object description: The processed param values for template variables in template - example: - 1: "Chatwoot" + example: + 1: 'Chatwoot' diff --git a/swagger/definitions/request/conversation/create_payload.yml b/swagger/definitions/request/conversation/create_payload.yml new file mode 100644 index 000000000..c0cc75c46 --- /dev/null +++ b/swagger/definitions/request/conversation/create_payload.yml @@ -0,0 +1,79 @@ +type: object +required: + - source_id + - inbox_id +properties: + source_id: + type: string + description: Conversation source id + example: '1234567890' + inbox_id: + type: integer + description: 'Id of inbox in which the conversation is created Allowed Inbox Types: Website, Phone, Api, Email' + example: 1 + contact_id: + type: integer + description: Contact Id for which conversation is created + example: 1 + additional_attributes: + type: object + description: Lets you specify attributes like browser information + example: + { + browser: 'Chrome', + browser_version: '89.0.4389.82', + os: 'Windows', + os_version: '10', + } + custom_attributes: + type: object + description: The object to save custom attributes for conversation, accepts custom attributes key and value + example: { attribute_key: attribute_value, priority_conversation_number: 3 } + status: + type: string + enum: ['open', 'resolved', 'pending'] + description: Specify the conversation whether it's pending, open, closed + example: open + assignee_id: + type: integer + description: Agent Id for assigning a conversation to an agent + example: 1 + team_id: + type: integer + description: Team Id for assigning a conversation to a team\ + example: 1 + snoozed_until: + type: string + format: date-time + description: Snoozed until date time + example: '2030-07-21T17:32:28Z' + message: + type: object + description: The initial message to be sent to the conversation + required: ['content'] + properties: + content: + type: string + description: The content of the message + example: 'Hello, how can I help you?' + template_params: + type: object + description: The template params for the message in case of whatsapp Channel + properties: + name: + type: string + description: Name of the template + example: 'sample_issue_resolution' + category: + type: string + description: Category of the template + example: UTILITY + language: + type: string + description: Language of the template + example: en_US + processed_params: + type: object + description: The processed param values for template variables in template + example: + 1: 'Chatwoot' diff --git a/swagger/definitions/request/custom_attribute/create_update_payload.yml b/swagger/definitions/request/custom_attribute/create_update_payload.yml index fd368f98c..3ee960559 100644 --- a/swagger/definitions/request/custom_attribute/create_update_payload.yml +++ b/swagger/definitions/request/custom_attribute/create_update_payload.yml @@ -3,20 +3,34 @@ properties: attribute_display_name: type: string description: Attribute display name + example: 'Custom Attribute' attribute_display_type: type: integer description: Attribute display type (text- 0, number- 1, currency- 2, percent- 3, link- 4, date- 5, list- 6, checkbox- 7) + example: 0 attribute_description: type: string description: Attribute description + example: 'This is a custom attribute' attribute_key: type: string description: Attribute unique key value + example: 'custom_attribute' attribute_values: type: array description: Attribute values items: type: string + example: ['value1', 'value2'] attribute_model: type: integer description: Attribute type(conversation_attribute- 0, contact_attribute- 1) + example: 0 + regex_pattern: + type: string + description: Regex pattern (Only applicable for type- text). The regex pattern is used to validate the attribute value(s). + example: '^[a-zA-Z0-9]+$' + regex_cue: + type: string + description: Regex cue message (Only applicable for type- text). The cue message is shown when the regex pattern is not matched. + example: 'Please enter a valid value' diff --git a/swagger/definitions/request/custom_filter/create_update_payload.yml b/swagger/definitions/request/custom_filter/create_update_payload.yml index 86a6cd8c1..c046f74df 100644 --- a/swagger/definitions/request/custom_filter/create_update_payload.yml +++ b/swagger/definitions/request/custom_filter/create_update_payload.yml @@ -3,10 +3,13 @@ properties: name: type: string description: The name of the custom filter + example: 'My Custom Filter' type: type: string - enum: ["conversation", "contact", "report"] + enum: ['conversation', 'contact', 'report'] description: The description about the custom filter + example: 'conversation' query: type: object description: A query that needs to be saved as a custom filter + example: {} diff --git a/swagger/definitions/request/inbox/create_payload.yml b/swagger/definitions/request/inbox/create_payload.yml new file mode 100644 index 000000000..59584054c --- /dev/null +++ b/swagger/definitions/request/inbox/create_payload.yml @@ -0,0 +1,88 @@ +type: object +properties: + name: + type: string + description: The name of the inbox + example: 'Support' + avatar: + type: string + format: binary + description: Image file for avatar + greeting_enabled: + type: boolean + description: Enable greeting message + example: true + greeting_message: + type: string + description: Greeting message to be displayed on the widget + example: Hello, how can I help you? + enable_email_collect: + type: boolean + description: Enable email collection + example: true + csat_survey_enabled: + type: boolean + description: Enable CSAT survey + example: true + enable_auto_assignment: + type: boolean + description: Enable Auto Assignment + example: true + working_hours_enabled: + type: boolean + description: Enable working hours + example: true + out_of_office_message: + type: string + description: Out of office message to be displayed on the widget + example: We are currently out of office. Please leave a message and we will get back to you. + timezone: + type: string + description: Timezone of the inbox + example: 'America/New_York' + allow_messages_after_resolved: + type: boolean + description: Allow messages after conversation is resolved + example: true + lock_to_single_conversation: + type: boolean + description: Lock to single conversation + example: true + portal_id: + type: integer + description: Id of the help center portal to attach to the inbox + example: 1 + sender_name_type: + type: string + description: Sender name type for the inbox + enum: ['friendly', 'professional'] + example: 'friendly' + business_name: + type: string + description: Business name for the inbox + example: 'My Business' + channel: + type: object + properties: + type: + type: string + description: Type of the channel + enum: + ['web_widget', 'api', 'email', 'line', 'telegram', 'whatsapp', 'sms'] + example: web_widget + website_url: + type: string + description: URL at which the widget will be loaded + example: 'https://example.com' + welcome_title: + type: string + description: Welcome title to be displayed on the widget + example: 'Welcome to our support' + welcome_tagline: + type: string + description: Welcome tagline to be displayed on the widget + example: 'We are here to help you' + widget_color: + type: string + description: A Hex-color string used to customize the widget + example: '#FF5733' diff --git a/swagger/definitions/request/inbox/update_payload.yml b/swagger/definitions/request/inbox/update_payload.yml new file mode 100644 index 000000000..f625fc5b8 --- /dev/null +++ b/swagger/definitions/request/inbox/update_payload.yml @@ -0,0 +1,82 @@ +type: object +properties: + name: + type: string + description: The name of the inbox + example: 'Support' + avatar: + type: string + format: binary + description: Image file for avatar + greeting_enabled: + type: boolean + description: Enable greeting message + example: true + greeting_message: + type: string + description: Greeting message to be displayed on the widget + example: Hello, how can I help you? + enable_email_collect: + type: boolean + description: Enable email collection + example: true + csat_survey_enabled: + type: boolean + description: Enable CSAT survey + example: true + enable_auto_assignment: + type: boolean + description: Enable Auto Assignment + example: true + working_hours_enabled: + type: boolean + description: Enable working hours + example: true + out_of_office_message: + type: string + description: Out of office message to be displayed on the widget + example: We are currently out of office. Please leave a message and we will get back to you. + timezone: + type: string + description: Timezone of the inbox + example: 'America/New_York' + allow_messages_after_resolved: + type: boolean + description: Allow messages after conversation is resolved + example: true + lock_to_single_conversation: + type: boolean + description: Lock to single conversation + example: true + portal_id: + type: integer + description: Id of the help center portal to attach to the inbox + example: 1 + sender_name_type: + type: string + description: Sender name type for the inbox + enum: ['friendly', 'professional'] + example: 'friendly' + business_name: + type: string + description: Business name for the inbox + example: 'My Business' + channel: + type: object + properties: + website_url: + type: string + description: URL at which the widget will be loaded + example: 'https://example.com' + welcome_title: + type: string + description: Welcome title to be displayed on the widget + example: 'Welcome to our support' + welcome_tagline: + type: string + description: Welcome tagline to be displayed on the widget + example: 'We are here to help you' + widget_color: + type: string + description: A Hex-color string used to customize the widget + example: '#FF5733' diff --git a/swagger/definitions/request/integrations/hook_create_payload.yml b/swagger/definitions/request/integrations/hook_create_payload.yml index d1bf518d8..71da8106b 100644 --- a/swagger/definitions/request/integrations/hook_create_payload.yml +++ b/swagger/definitions/request/integrations/hook_create_payload.yml @@ -1,11 +1,18 @@ type: object properties: app_id: - type: string + type: integer description: The ID of app for which integration hook is being created + example: 1 inbox_id: - type: string + type: integer description: The inbox ID, if the hook is an inbox hook + example: 1 + status: + type: integer + description: The status of the integration (0 for inactive, 1 for active) + example: 1 settings: type: object description: The settings required by the integration + example: {} diff --git a/swagger/definitions/request/integrations/hook_update_payload.yml b/swagger/definitions/request/integrations/hook_update_payload.yml index 5b91024db..4444dee81 100644 --- a/swagger/definitions/request/integrations/hook_update_payload.yml +++ b/swagger/definitions/request/integrations/hook_update_payload.yml @@ -1,5 +1,10 @@ type: object properties: + status: + type: integer + description: The status of the integration (0 for inactive, 1 for active) + example: 1 settings: type: object description: The settings required by the integration + example: {} diff --git a/swagger/definitions/request/platform/agent_bot/create_update_payload.yml b/swagger/definitions/request/platform/agent_bot/create_update_payload.yml new file mode 100644 index 000000000..a258e28a6 --- /dev/null +++ b/swagger/definitions/request/platform/agent_bot/create_update_payload.yml @@ -0,0 +1,26 @@ +type: object +properties: + name: + type: string + description: The name of the agent bot + example: 'My Agent Bot' + description: + type: string + description: The description of the agent bot + example: 'This is a sample agent bot' + outgoing_url: + type: string + description: The webhook URL for the bot + example: 'https://example.com/webhook' + account_id: + type: integer + description: The account ID to associate the agent bot with + example: 1 + avatar: + type: string + format: binary + description: Send the form data with the avatar image binary or use the avatar_url + avatar_url: + type: string + description: The url to a jpeg, png file for the agent bot avatar + example: https://example.com/avatar.png diff --git a/swagger/definitions/request/portal/article_create_update_payload.yml b/swagger/definitions/request/portal/article_create_update_payload.yml index cddac1d1d..311d478c3 100644 --- a/swagger/definitions/request/portal/article_create_update_payload.yml +++ b/swagger/definitions/request/portal/article_create_update_payload.yml @@ -1,34 +1,51 @@ type: object properties: - content: + title: type: string - description: The text content. - meta: - type: object - description: Use for search - example: { tags: ['article_name'], title: 'article title', description: 'article description' } + description: The title of the article + example: 'Article Title' + slug: + type: string + description: The slug of the article + example: 'article-title' position: type: integer description: article position in category - status: - type: integer - example: ['draft', 'published', 'archived'] - title: + example: 1 + content: type: string - slug: + description: The text content. + example: 'This is the content of the article' + description: type: string - views: - type: integer - portal_id: - type: integer - account_id: - type: integer - author_id: - type: integer + description: The description of the article + example: 'This is the description of the article' category_id: type: integer - folder_id: + description: The category id of the article + example: 1 + author_id: type: integer + description: The author agent id of the article + example: 1 associated_article_id: type: integer description: To associate similar articles to each other, e.g to provide the link for the reference. + example: 2 + status: + type: integer + description: The status of the article. 0 for draft, 1 for published, 2 for archived + example: 1 + locale: + type: string + description: The locale of the article + example: 'en' + meta: + type: object + description: Use for search + example: + { + tags: ['article_name'], + title: 'article title', + description: 'article description', + } diff --git a/swagger/definitions/request/portal/category_create_update_payload.yml b/swagger/definitions/request/portal/category_create_update_payload.yml index 0d1ce5c8f..67bb650cb 100644 --- a/swagger/definitions/request/portal/category_create_update_payload.yml +++ b/swagger/definitions/request/portal/category_create_update_payload.yml @@ -1,28 +1,34 @@ type: object properties: - description: - type: string - description: Category description - locale: - type: string - description: Category locale - example: en/es name: type: string - description: Category name - slug: + description: The name of the category + example: 'Category Name' + description: type: string - description: Category slug + description: A description for the category + example: 'Category description' position: type: integer description: Category position in the portal list to sort - portal_id: - type: integer - account_id: - type: integer - associated_category_id: - type: integer - description: To associate similar categories to each other, e.g same category of product documentation in different languages + example: 1 + slug: + type: string + description: The category slug used in the URL + example: 'category-name' + locale: + type: string + description: The locale of the category + example: en + icon: + type: string + description: The icon of the category as a string (emoji) + example: '📚' parent_category_id: type: integer description: To define parent category, e.g product documentation has multiple level features in sales category or in engineering category. + example: 1 + associated_category_id: + type: integer + description: To associate similar categories to each other, e.g same category of product documentation in different languages + example: 2 diff --git a/swagger/definitions/request/portal/portal_create_update_payload.yml b/swagger/definitions/request/portal/portal_create_update_payload.yml index d1e82aaf4..56feadbda 100644 --- a/swagger/definitions/request/portal/portal_create_update_payload.yml +++ b/swagger/definitions/request/portal/portal_create_update_payload.yml @@ -1,20 +1,13 @@ type: object properties: - archived: - type: boolean - description: Status to check if portal is live color: type: string - description: Header color for help-center - example: add color HEX string, "#fffff" - config: - type: object - description: Configuration about supporting locales - example: { allowed_locales: ['en', 'es'], default_locale: 'en' } + description: Header color for help-center in hex format + example: '#FFFFFF' custom_domain: type: string - description: Custom domain to display help center. - example: https://chatwoot.help/. + description: Custom domain to display help center. + example: chatwoot.help header_text: type: string description: Help center header @@ -26,11 +19,20 @@ properties: name: type: string description: Name for the portal - slug: - type: string - description: Slug for the portal to display in link + example: Handbook page_title: type: string description: Page title for the portal - account_id: - type: integer + example: Handbook + slug: + type: string + description: Slug for the portal to display in link + example: handbook + archived: + type: boolean + description: Status to check if portal is live + example: false + config: + type: object + description: Configuration about supporting locales + example: { allowed_locales: ['en', 'es'], default_locale: 'en' } diff --git a/swagger/definitions/request/public/contact/create_update_payload.yml b/swagger/definitions/request/public/contact/create_update_payload.yml index 48c6e4550..f4cacafe8 100644 --- a/swagger/definitions/request/public/contact/create_update_payload.yml +++ b/swagger/definitions/request/public/contact/create_update_payload.yml @@ -1,24 +1,30 @@ - type: object properties: identifier: type: string description: External identifier of the contact + example: '1234567890' identifier_hash: type: string description: Identifier hash prepared for HMAC authentication + example: 'e93275d4eba0e5679ad55f5360af00444e2a888df9b0afa3e8b691c3173725f9' email: type: string description: Email of the contact + example: alice@acme.inc name: type: string description: Name of the contact + example: Alice phone_number: type: string description: Phone number of the contact - avatar_url: + example: '+123456789' + avatar: type: string - description: The url to a jpeg, png file for the user avatar + format: binary + description: Send the form data with the avatar image binary or use the avatar_url custom_attributes: type: object - description: Custom attributes of the customer \ No newline at end of file + description: Custom attributes of the customer + example: {} diff --git a/swagger/definitions/request/public/conversation/create_payload.yml b/swagger/definitions/request/public/conversation/create_payload.yml index 37cd9f7ef..e81d1adf5 100644 --- a/swagger/definitions/request/public/conversation/create_payload.yml +++ b/swagger/definitions/request/public/conversation/create_payload.yml @@ -3,3 +3,4 @@ properties: custom_attributes: type: object description: Custom attributes of the conversation + example: {} diff --git a/swagger/definitions/request/public/message/create_payload.yml b/swagger/definitions/request/public/message/create_payload.yml index 2935b4abb..6ae920255 100644 --- a/swagger/definitions/request/public/message/create_payload.yml +++ b/swagger/definitions/request/public/message/create_payload.yml @@ -1,10 +1,10 @@ - type: object properties: content: type: string description: Content for the message + example: 'Hello, how can I help you?' echo_id: type: string description: Temporary identifier which will be passed back via websockets - \ No newline at end of file + example: '1234567890' diff --git a/swagger/definitions/request/public/message/update_payload.yml b/swagger/definitions/request/public/message/update_payload.yml index 10926faa1..c739aa34a 100644 --- a/swagger/definitions/request/public/message/update_payload.yml +++ b/swagger/definitions/request/public/message/update_payload.yml @@ -1,6 +1,30 @@ - type: object properties: submitted_values: type: object - description: Replies to the Bot Message Types \ No newline at end of file + description: Replies to the Bot Message Types + properties: + name: + type: string + description: The name of the submiitted value + example: 'My Name' + title: + type: string + description: The title of the submitted value + example: 'My Title' + value: + type: string + description: The value of the submitted value + example: 'value' + csat_survey_response: + type: object + description: The CSAT survey response + properties: + feedback_message: + type: string + description: The feedback message of the CSAT survey response + example: 'Great service!' + rating: + type: integer + description: The rating of the CSAT survey response + example: 5 diff --git a/swagger/definitions/request/team/create_update_payload.yml b/swagger/definitions/request/team/create_update_payload.yml index f4db1e5d7..77b714269 100644 --- a/swagger/definitions/request/team/create_update_payload.yml +++ b/swagger/definitions/request/team/create_update_payload.yml @@ -3,9 +3,12 @@ properties: name: type: string description: The name of the team + example: Support Team description: type: string description: The description of the team + example: This is a team of support agents allow_auto_assign: type: boolean description: If this setting is turned on, the system would automatically assign the conversation to an agent in the team while assigning the conversation to a team + example: true diff --git a/swagger/definitions/request/user/create_update_payload.yml b/swagger/definitions/request/user/create_update_payload.yml index e14a25317..1bebdc59c 100644 --- a/swagger/definitions/request/user/create_update_payload.yml +++ b/swagger/definitions/request/user/create_update_payload.yml @@ -3,13 +3,20 @@ properties: name: type: string description: Name of the user + example: 'Daniel' + display_name: + type: string + description: Display name of the user + example: 'Dan' email: type: string description: Email of the user + example: 'daniel@acme.inc' password: type: string description: Password must contain uppercase, lowercase letters, number and a special character + example: 'Password2!' custom_attributes: type: object description: Custom attributes you want to associate with the user - + example: {} diff --git a/swagger/definitions/request/webhooks/create_update_payload.yml b/swagger/definitions/request/webhooks/create_update_payload.yml index 417843fe6..2f4a718e4 100644 --- a/swagger/definitions/request/webhooks/create_update_payload.yml +++ b/swagger/definitions/request/webhooks/create_update_payload.yml @@ -3,16 +3,23 @@ properties: url: type: string description: The url where the events should be sent + example: https://example.com/webhook subscriptions: type: array items: type: string - enum: [ - "conversation_created", - "conversation_status_changed", - "conversation_updated", - "message_created", - "message_updated", - "webwidget_triggered" - ] + enum: + [ + 'conversation_created', + 'conversation_status_changed', + 'conversation_updated', + 'message_created', + 'message_updated', + 'contact_created', + 'contact_updated', + 'webwidget_triggered', + ] description: The events you want to subscribe to. + example: + - conversation_created + - conversation_status_changed diff --git a/swagger/definitions/resource/account_user.yml b/swagger/definitions/resource/account_user.yml new file mode 100644 index 000000000..cfc530383 --- /dev/null +++ b/swagger/definitions/resource/account_user.yml @@ -0,0 +1,14 @@ +type: array +description: 'Array of account users' +items: + type: object + properties: + account_id: + type: integer + description: The ID of the account + user_id: + type: integer + description: The ID of the user + role: + type: string + description: whether user is an administrator or agent diff --git a/swagger/definitions/resource/agent.yml b/swagger/definitions/resource/agent.yml index e3d506a3b..5287f3e6f 100644 --- a/swagger/definitions/resource/agent.yml +++ b/swagger/definitions/resource/agent.yml @@ -2,31 +2,34 @@ type: object properties: id: type: integer - uid: - type: string - name: - type: string - available_name: - type: string - display_name: - type: string - email: - type: string account_id: type: integer - role: - type: string - enum: ['agent', 'administrator'] - confirmed: - type: boolean - availability_status: + availability_status: type: string enum: ['available', 'busy', 'offline'] description: The availability status of the agent computed by Chatwoot. auto_offline: type: boolean description: Whether the availability status of agent is configured to go offline automatically when away. - custom_attributes: - type: object - description: Available for users who are created through platform APIs and has custom attributes associated. - + confirmed: + type: boolean + description: Whether the agent has confirmed their email address. + email: + type: string + description: The email of the agent + available_name: + type: string + description: The available name of the agent + name: + type: string + description: The name of the agent + role: + type: string + enum: ['agent', 'administrator'] + description: The role of the agent + thumbnail: + type: string + description: The thumbnail of the agent + custom_role_id: + type: integer + description: The custom role id of the agent diff --git a/swagger/definitions/resource/agent_bot.yml b/swagger/definitions/resource/agent_bot.yml index d94777d37..963b4f0b9 100644 --- a/swagger/definitions/resource/agent_bot.yml +++ b/swagger/definitions/resource/agent_bot.yml @@ -9,9 +9,24 @@ properties: description: type: string description: The description about the agent bot - account_id: - type: number - description: Account ID if it's an account specific bot + thumbnail: + type: string + description: The thumbnail of the agent bot outgoing_url: type: string description: The webhook URL for the bot + bot_type: + type: string + description: The type of the bot + bot_config: + type: object + description: The configuration of the bot + account_id: + type: number + description: Account ID if it's an account specific bot + access_token: + type: string + description: The access token for the bot + system_bot: + type: boolean + description: Whether the bot is a system bot diff --git a/swagger/definitions/resource/automation_rule.yml b/swagger/definitions/resource/automation_rule.yml index 4a3a03d01..b561441ff 100644 --- a/swagger/definitions/resource/automation_rule.yml +++ b/swagger/definitions/resource/automation_rule.yml @@ -1,45 +1,13 @@ type: object properties: - event_name: - type: string - description: Automation Rule event, on which we call the actions(conversation_created, conversation_updated, message_created) - enum: - - conversation_created - - conversation_updated - - message_created - example: message_created - name: - type: string - description: The name of the rule - example: Add label on message create event - description: - type: string - description: Description to give more context about the rule - example: Add label support and sales on message create event if incoming message content contains text help - active: - type: boolean - description: Enable/disable automation rule - actions: - type: array - description: Array of actions which we perform when condition matches - items: - type: object - example: - action_name: add_label - action_params: - - support - - sales - conditions: - type: array - description: Array of conditions on which conversation/message filter would work - items: - type: object - example: - attribute_key: content - filter_operator: contains - values: - - help - query_operator: nil - account_id: - type: integer - description: Account Id + payload: + description: Response payload that contains automation rule(s) + oneOf: + - type: array + description: Array of automation rules (for listing endpoint) + items: + $ref: '#/components/schemas/automation_rule_item' + - type: object + description: Single automation rule (for show/create/update endpoints) + allOf: + - $ref: '#/components/schemas/automation_rule_item' \ No newline at end of file diff --git a/swagger/definitions/resource/automation_rule_item.yml b/swagger/definitions/resource/automation_rule_item.yml new file mode 100644 index 000000000..b3b644b2e --- /dev/null +++ b/swagger/definitions/resource/automation_rule_item.yml @@ -0,0 +1,69 @@ +type: object +properties: + id: + type: integer + description: The ID of the automation rule + account_id: + type: integer + description: Account Id + name: + type: string + description: The name of the rule + example: Add label on message create event + description: + type: string + description: Description to give more context about the rule + example: Add label support and sales on message create event if incoming message content contains text help + event_name: + type: string + description: Automation Rule event, on which we call the actions(conversation_created, conversation_updated, message_created) + enum: + - conversation_created + - conversation_updated + - message_created + example: message_created + conditions: + type: array + description: Array of conditions on which conversation/message filter would work + items: + type: object + properties: + values: + type: array + items: + type: string + attribute_key: + type: string + query_operator: + type: string + filter_operator: + type: string + example: + attribute_key: content + filter_operator: contains + values: + - help + query_operator: and + actions: + type: array + description: Array of actions which we perform when condition matches + items: + type: object + properties: + action_name: + type: string + action_params: + type: array + items: + type: string + example: + action_name: add_label + action_params: + - support + - sales + created_on: + type: integer + description: The timestamp when the rule was created + active: + type: boolean + description: Enable/disable automation rule \ No newline at end of file diff --git a/swagger/definitions/resource/canned_response.yml b/swagger/definitions/resource/canned_response.yml index b044b703a..8e4f73370 100644 --- a/swagger/definitions/resource/canned_response.yml +++ b/swagger/definitions/resource/canned_response.yml @@ -3,12 +3,18 @@ properties: id: type: integer description: ID of the canned response - content: - type: string - description: Message content for canned response - short_code: - type: string - description: Short Code for quick access of the canned response account_id: type: integer description: Account Id + short_code: + type: string + description: Short Code for quick access of the canned response + content: + type: string + description: Message content for canned response + created_at: + type: string + description: The date and time when the canned response was created + updated_at: + type: string + description: The date and time when the canned response was updated diff --git a/swagger/definitions/resource/contact.yml b/swagger/definitions/resource/contact.yml index bbb82e14c..259604f0d 100644 --- a/swagger/definitions/resource/contact.yml +++ b/swagger/definitions/resource/contact.yml @@ -1,31 +1,49 @@ type: object properties: payload: - type: object - properties: - contact: - type: object - properties: - email: - type: string - description: Email address of the contact - name: - type: string - description: The name of the contact - phone_number: - type: string - description: Phone number of the contact - thumbnail: - type: string - description: Avatar URL of the contact - additional_attributes: - type: object - description: The object containing additional attributes related to the contact - custom_attributes: - type: object - description: The object to save custom attributes for contact, accepts custom attributes key and value - example: { attribute_key: attribute_value, signed_up_at: dd/mm/yyyy } - contact_inboxes: - type: array - items: - $ref: '#/definitions/contact_inboxes' + type: array + items: + type: object + properties: + additional_attributes: + type: object + description: The object containing additional attributes related to the contact + availability_status: + type: string + description: The availability status of the contact + email: + type: string + description: The email address of the contact + id: + type: integer + description: The ID of the contact + name: + type: string + description: The name of the contact + phone_number: + type: string + description: The phone number of the contact + blocked: + type: boolean + description: Whether the contact is blocked + identifier: + type: string + description: The identifier of the contact + thumbnail: + type: string + description: The thumbnail of the contact + custom_attributes: + type: object + description: The custom attributes of the contact + example: + { attribute_key: attribute_value, signed_up_at: dd/mm/yyyy } + last_activity_at: + type: integer + description: The last activity at of the contact + created_at: + type: integer + description: The created at of the contact + contact_inboxes: + type: array + items: + $ref: '#/components/schemas/contact_inboxes' diff --git a/swagger/definitions/resource/contact_conversation_message.yml b/swagger/definitions/resource/contact_conversation_message.yml new file mode 100644 index 000000000..e95923e52 --- /dev/null +++ b/swagger/definitions/resource/contact_conversation_message.yml @@ -0,0 +1,109 @@ +type: object +properties: + id: + type: integer + description: ID of the message + content: + type: string + description: Content of the message + account_id: + type: integer + description: ID of the account + inbox_id: + type: integer + description: ID of the inbox + conversation_id: + type: integer + description: ID of the conversation + message_type: + type: integer + description: Type of the message + created_at: + type: integer + description: Timestamp when message was created + updated_at: + type: string + description: Formatted datetime when message was updated + private: + type: boolean + description: Whether the message is private + status: + type: string + description: Status of the message + source_id: + type: string + description: Source ID of the message + nullable: true + content_type: + type: string + description: Type of the content + content_attributes: + type: object + description: Attributes of the content + sender_type: + type: string + description: Type of the sender + nullable: true + sender_id: + type: integer + description: ID of the sender + nullable: true + external_source_ids: + type: object + description: External source IDs + additional_attributes: + type: object + description: Additional attributes of the message + processed_message_content: + type: string + description: Processed message content + nullable: true + sentiment: + type: object + description: Sentiment analysis of the message + conversation: + type: object + description: Conversation details + properties: + assignee_id: + type: integer + description: ID of the assignee + nullable: true + unread_count: + type: integer + description: Count of unread messages + last_activity_at: + type: integer + description: Timestamp of last activity + contact_inbox: + type: object + description: Contact inbox details + properties: + source_id: + type: string + description: Source ID of the contact inbox + sender: + type: object + description: Details of the sender + properties: + id: + type: integer + description: ID of the sender + name: + type: string + description: Name of the sender + available_name: + type: string + description: Available name of the sender + avatar_url: + type: string + description: URL of the sender's avatar + type: + type: string + description: Type of the sender + availability_status: + type: string + description: Availability status of the sender + thumbnail: + type: string + description: Thumbnail URL of the sender \ No newline at end of file diff --git a/swagger/definitions/resource/contact_conversations_response.yml b/swagger/definitions/resource/contact_conversations_response.yml new file mode 100644 index 000000000..9e77858a8 --- /dev/null +++ b/swagger/definitions/resource/contact_conversations_response.yml @@ -0,0 +1,13 @@ +type: object +properties: + payload: + type: array + items: + allOf: + - $ref: '#/components/schemas/conversation' + - type: object + properties: + meta: + $ref: './extension/contact/conversation.yml#/properties/meta' + description: Meta information about the conversation + description: List of conversations for the contact \ No newline at end of file diff --git a/swagger/definitions/resource/contact_detail.yml b/swagger/definitions/resource/contact_detail.yml new file mode 100644 index 000000000..060c91214 --- /dev/null +++ b/swagger/definitions/resource/contact_detail.yml @@ -0,0 +1,48 @@ +type: object +properties: + additional_attributes: + type: object + description: The object containing additional attributes related to the contact + properties: + city: + type: string + description: City of the contact + country: + type: string + description: Country of the contact + country_code: + type: string + description: Country code of the contact + created_at_ip: + type: string + description: IP address when the contact was created + custom_attributes: + type: object + description: The custom attributes of the contact + email: + type: string + description: The email address of the contact + id: + type: integer + description: The ID of the contact + identifier: + type: string + description: The identifier of the contact + nullable: true + name: + type: string + description: The name of the contact + phone_number: + type: string + description: The phone number of the contact + nullable: true + thumbnail: + type: string + description: The thumbnail of the contact + blocked: + type: boolean + description: Whether the contact is blocked + type: + type: string + description: The type of entity + enum: ["contact"] \ No newline at end of file diff --git a/swagger/definitions/resource/contact_inbox.yml b/swagger/definitions/resource/contact_inbox.yml new file mode 100644 index 000000000..34fd374f2 --- /dev/null +++ b/swagger/definitions/resource/contact_inbox.yml @@ -0,0 +1,27 @@ +type: object +properties: + source_id: + type: string + description: Source identifier for the contact inbox + inbox: + type: object + properties: + id: + type: integer + description: ID of the inbox + avatar_url: + type: string + description: URL for the inbox avatar + channel_id: + type: integer + description: ID of the channel + name: + type: string + description: Name of the inbox + channel_type: + type: string + description: Type of channel + provider: + type: string + description: Provider of the inbox + nullable: true \ No newline at end of file diff --git a/swagger/definitions/resource/contact_inboxes.yml b/swagger/definitions/resource/contact_inboxes.yml index a223490ff..5eabeff13 100644 --- a/swagger/definitions/resource/contact_inboxes.yml +++ b/swagger/definitions/resource/contact_inboxes.yml @@ -4,5 +4,4 @@ properties: type: string description: Contact Inbox Source Id inbox: - type: object - $ref: '#/definitions/inbox' + $ref: '#/components/schemas/inbox_contact' diff --git a/swagger/definitions/resource/contact_list_item.yml b/swagger/definitions/resource/contact_list_item.yml new file mode 100644 index 000000000..7765b2265 --- /dev/null +++ b/swagger/definitions/resource/contact_list_item.yml @@ -0,0 +1,61 @@ +type: object +properties: + additional_attributes: + type: object + description: The object containing additional attributes related to the contact + properties: + city: + type: string + description: City of the contact + country: + type: string + description: Country of the contact + country_code: + type: string + description: Country code of the contact + created_at_ip: + type: string + description: IP address when the contact was created + availability_status: + type: string + description: Availability status of the contact + enum: ["online", "offline"] + email: + type: string + description: The email address of the contact + nullable: true + id: + type: integer + description: The ID of the contact + name: + type: string + description: The name of the contact + phone_number: + type: string + description: The phone number of the contact + nullable: true + blocked: + type: boolean + description: Whether the contact is blocked + identifier: + type: string + description: The identifier of the contact + nullable: true + thumbnail: + type: string + description: The thumbnail of the contact + custom_attributes: + type: object + description: The custom attributes of the contact + last_activity_at: + type: integer + description: Timestamp of last activity + nullable: true + created_at: + type: integer + description: Timestamp when contact was created + contact_inboxes: + type: array + description: List of inboxes associated with this contact + items: + $ref: '#/components/schemas/contact_inbox' \ No newline at end of file diff --git a/swagger/definitions/resource/contact_meta.yml b/swagger/definitions/resource/contact_meta.yml new file mode 100644 index 000000000..f7139b9d2 --- /dev/null +++ b/swagger/definitions/resource/contact_meta.yml @@ -0,0 +1,8 @@ +type: object +properties: + count: + type: integer + description: Total number of contacts + current_page: + type: string + description: Current page number \ No newline at end of file diff --git a/swagger/definitions/resource/contact_show_response.yml b/swagger/definitions/resource/contact_show_response.yml new file mode 100644 index 000000000..e4f861271 --- /dev/null +++ b/swagger/definitions/resource/contact_show_response.yml @@ -0,0 +1,5 @@ +type: object +properties: + payload: + $ref: '#/components/schemas/contact_list_item' + description: Contact details \ No newline at end of file diff --git a/swagger/definitions/resource/contactable_inboxes.yml b/swagger/definitions/resource/contactable_inboxes.yml index a223490ff..7ccba61b1 100644 --- a/swagger/definitions/resource/contactable_inboxes.yml +++ b/swagger/definitions/resource/contactable_inboxes.yml @@ -5,4 +5,4 @@ properties: description: Contact Inbox Source Id inbox: type: object - $ref: '#/definitions/inbox' + $ref: '#/components/schemas/inbox' diff --git a/swagger/definitions/resource/contactable_inboxes_response.yml b/swagger/definitions/resource/contactable_inboxes_response.yml new file mode 100644 index 000000000..5af3e7dc8 --- /dev/null +++ b/swagger/definitions/resource/contactable_inboxes_response.yml @@ -0,0 +1,7 @@ +type: object +properties: + payload: + type: array + items: + $ref: '#/components/schemas/contact_inbox' + description: List of contactable inboxes for the contact \ No newline at end of file diff --git a/swagger/definitions/resource/contacts_list_response.yml b/swagger/definitions/resource/contacts_list_response.yml new file mode 100644 index 000000000..75a63b5c3 --- /dev/null +++ b/swagger/definitions/resource/contacts_list_response.yml @@ -0,0 +1,10 @@ +type: object +properties: + meta: + $ref: '#/components/schemas/contact_meta' + description: Metadata about the contact list response + payload: + type: array + items: + $ref: '#/components/schemas/contact_list_item' + description: List of contacts \ No newline at end of file diff --git a/swagger/definitions/resource/conversation.yml b/swagger/definitions/resource/conversation.yml index 085d21879..c1577e693 100644 --- a/swagger/definitions/resource/conversation.yml +++ b/swagger/definitions/resource/conversation.yml @@ -6,31 +6,86 @@ properties: messages: type: array items: - $ref: '#/definitions/message' + $ref: '#/components/schemas/message' account_id: type: number description: Account Id + uuid: + type: string + description: UUID of the conversation + additional_attributes: + type: object + description: The object containing additional attributes related to the conversation + agent_last_seen_at: + type: number + description: The last activity at of the agent + assignee_last_seen_at: + type: number + description: The last activity at of the assignee + can_reply: + type: boolean + description: Whether the conversation can be replied to + contact_last_seen_at: + type: number + description: The last activity at of the contact + custom_attributes: + type: object + description: The object to save custom attributes for conversation, accepts custom attributes key and value inbox_id: type: number description: ID of the inbox + labels: + type: array + items: + type: string + description: The labels of the conversation + muted: + type: boolean + description: Whether the conversation is muted + snoozed_until: + type: number + description: The time at which the conversation will be unmuted status: type: string enum: ['open', 'resolved', 'pending'] description: The status of the conversation + created_at: + type: number + description: The time at which conversation was created + updated_at: + type: number + description: The time at which conversation was updated timestamp: type: string description: The time at which conversation was created - contact_last_seen_at: - type: string - agent_last_seen_at: - type: string + first_reply_created_at: + type: number + description: The time at which the first reply was created unread_count: type: number description: The number of unread messages - additional_attributes: + last_non_activity_message: type: object - description: The object containing additional attributes related to the conversation - custom_attributes: + $ref: '#/components/schemas/message' + description: The last non activity message + last_activity_at: + type: number + description: The last activity at of the conversation + priority: + type: string + description: The priority of the conversation + waiting_since: + type: number + description: The time at which the conversation was waiting + sla_policy_id: + type: number + description: The ID of the SLA policy + applied_sla: type: object - description: The object to save custom attributes for conversation, accepts custom attributes key and value - example: { attribute_key: attribute_value, priority_conversation_number: 3 } + description: The applied SLA + sla_events: + type: array + items: + type: object + description: SLA event objects + diff --git a/swagger/definitions/resource/conversation_messages.yml b/swagger/definitions/resource/conversation_messages.yml new file mode 100644 index 000000000..904f2f2d4 --- /dev/null +++ b/swagger/definitions/resource/conversation_messages.yml @@ -0,0 +1,10 @@ +type: object +properties: + meta: + $ref: '#/components/schemas/conversation_meta' + description: Meta information about the conversation + payload: + type: array + items: + $ref: '#/components/schemas/message_detailed' + description: List of messages in the conversation \ No newline at end of file diff --git a/swagger/definitions/resource/conversation_meta.yml b/swagger/definitions/resource/conversation_meta.yml new file mode 100644 index 000000000..7cffc0fab --- /dev/null +++ b/swagger/definitions/resource/conversation_meta.yml @@ -0,0 +1,55 @@ +type: object +properties: + labels: + type: array + items: + type: string + description: Labels associated with the conversation + additional_attributes: + type: object + properties: + browser: + type: object + properties: + device_name: + type: string + description: Name of the device + browser_name: + type: string + description: Name of the browser + platform_name: + type: string + description: Name of the platform + browser_version: + type: string + description: Version of the browser + platform_version: + type: string + description: Version of the platform + referer: + type: string + description: Referrer URL + initiated_at: + type: object + properties: + timestamp: + type: string + description: Timestamp when the conversation was initiated + browser_language: + type: string + description: Browser language setting + conversation_language: + type: string + description: Conversation language + description: Additional attributes of the conversation + contact: + $ref: '#/components/schemas/contact_detail' + description: Contact details + agent_last_seen_at: + type: string + 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 \ No newline at end of file diff --git a/swagger/definitions/resource/custom_attribute.yml b/swagger/definitions/resource/custom_attribute.yml index ba36fd714..67247f003 100644 --- a/swagger/definitions/resource/custom_attribute.yml +++ b/swagger/definitions/resource/custom_attribute.yml @@ -15,15 +15,24 @@ properties: attribute_key: type: string description: Attribute unique key value + regex_pattern: + type: string + description: Regex pattern + regex_cue: + type: string + description: Regex cue attribute_values: type: string description: Attribute values - default_value: - type: string - description: Attribute default value attribute_model: type: string description: Attribute type(conversation_attribute/contact_attribute) - account_id: - type: integer - description: Account Id + default_value: + type: string + description: Attribute default value + created_at: + type: string + description: The date and time when the custom attribute was created + updated_at: + type: string + description: The date and time when the custom attribute was updated diff --git a/swagger/definitions/resource/extension/contact/conversation.yml b/swagger/definitions/resource/extension/contact/conversation.yml index 6e6576f80..ef1b0eb75 100644 --- a/swagger/definitions/resource/extension/contact/conversation.yml +++ b/swagger/definitions/resource/extension/contact/conversation.yml @@ -6,17 +6,48 @@ properties: sender: type: object properties: + additional_attributes: + type: object + description: The additional attributes of the sender + availability_status: + type: string + description: The availability status of the sender + email: + type: string + description: The email of the sender id: type: number description: ID fo the sender name: type: string description: The name of the sender + phone_number: + type: string + description: The phone number of the sender + blocked: + type: boolean + description: Whether the sender is blocked + identifier: + type: string + description: The identifier of the sender thumbnail: type: string description: Avatar URL of the contact - channel: - type: string - description: Channel Type + custom_attributes: + type: object + description: The custom attributes of the sender + last_activity_at: + type: number + description: The last activity at of the sender + created_at: + type: number + description: The created at of the sender + + channel: + type: string + description: Channel Type assignee: - $ref: '#/definitions/user' + $ref: '#/components/schemas/user' + hmac_verified: + type: boolean + description: Whether the hmac is verified diff --git a/swagger/definitions/resource/extension/conversation/list.yml b/swagger/definitions/resource/extension/conversation/list.yml index 426902d4f..39e04baa0 100644 --- a/swagger/definitions/resource/extension/conversation/list.yml +++ b/swagger/definitions/resource/extension/conversation/list.yml @@ -19,6 +19,6 @@ properties: description: 'array of conversations' items: allOf: - - $ref: '#/definitions/generic_id' - - $ref: '#/definitions/conversation' + - $ref: '#/components/schemas/generic_id' + - $ref: '#/components/schemas/conversation' - $ref: '../contact/conversation.yml' diff --git a/swagger/definitions/resource/extension/conversation/show.yml b/swagger/definitions/resource/extension/conversation/show.yml index fb697ad38..27a27018b 100644 --- a/swagger/definitions/resource/extension/conversation/show.yml +++ b/swagger/definitions/resource/extension/conversation/show.yml @@ -1,4 +1,4 @@ type: object allOf: - - $ref: '#/definitions/conversation' + - $ref: '#/components/schemas/conversation' - $ref: '../contact/conversation.yml' diff --git a/swagger/definitions/resource/inbox.yml b/swagger/definitions/resource/inbox.yml index bba46ea34..88f5ea288 100644 --- a/swagger/definitions/resource/inbox.yml +++ b/swagger/definitions/resource/inbox.yml @@ -39,3 +39,87 @@ properties: greeting_message: type: string description: A greeting message when the user starts the conversation + channel_id: + type: number + description: ID of the channel this inbox belongs to + working_hours_enabled: + type: boolean + description: The flag which shows whether working hours feature is enabled + enable_email_collect: + type: boolean + description: The flag to enable collecting email from contacts + csat_survey_enabled: + type: boolean + description: The flag to enable CSAT survey + auto_assignment_config: + type: object + description: Configuration settings for auto assignment + out_of_office_message: + type: string + description: Message to show when agents are out of office + working_hours: + type: array + description: Configuration for working hours of the inbox + items: + type: object + properties: + day_of_week: + type: number + description: Day of the week (0-6, where 0 is Sunday) + closed_all_day: + type: boolean + description: Whether the inbox is closed for the entire day + open_hour: + type: number + description: Hour when inbox opens (0-23) + open_minutes: + type: number + description: Minutes of the hour when inbox opens (0-59) + close_hour: + type: number + description: Hour when inbox closes (0-23) + close_minutes: + type: number + description: Minutes of the hour when inbox closes (0-59) + open_all_day: + type: boolean + description: Whether the inbox is open for the entire day + timezone: + type: string + description: Timezone configuration for the inbox + callback_webhook_url: + type: string + description: Webhook URL for callbacks + allow_messages_after_resolved: + type: boolean + description: Whether to allow messages after a conversation is resolved + lock_to_single_conversation: + type: boolean + description: Whether to lock a contact to a single conversation + sender_name_type: + type: string + description: Type of sender name to display (e.g., friendly) + business_name: + type: string + description: Business name associated with the inbox + hmac_mandatory: + type: boolean + description: Whether HMAC verification is mandatory + selected_feature_flags: + type: object + description: Selected feature flags for the inbox + reply_time: + type: string + description: Expected reply time + messaging_service_sid: + type: string + description: Messaging service SID for SMS providers + phone_number: + type: string + description: Phone number associated with the inbox + medium: + type: string + description: Medium of communication (e.g., sms, email) + provider: + type: string + description: Provider of the channel diff --git a/swagger/definitions/resource/inbox_contact.yml b/swagger/definitions/resource/inbox_contact.yml new file mode 100644 index 000000000..fcac3d821 --- /dev/null +++ b/swagger/definitions/resource/inbox_contact.yml @@ -0,0 +1,20 @@ +type: object +properties: + id: + type: number + description: ID of the inbox + avatar_url: + type: string + description: The avatar image of the inbox + channel_id: + type: number + description: The ID of the channel + name: + type: string + description: The name of the inbox + channel_type: + type: string + description: The type of the inbox + provider: + type: string + description: The provider of the inbox diff --git a/swagger/definitions/resource/message.yml b/swagger/definitions/resource/message.yml index 78c8fd640..f31936295 100644 --- a/swagger/definitions/resource/message.yml +++ b/swagger/definitions/resource/message.yml @@ -1,8 +1,40 @@ type: object properties: + id: + type: number + description: The ID of the message content: type: string description: The text content of the message + account_id: + type: number + description: The ID of the account + inbox_id: + type: number + description: The ID of the inbox + conversation_id: + type: number + description: The ID of the conversation + message_type: + type: integer + enum: [0, 1, 2] + description: The type of the message + created_at: + type: integer + description: The time at which message was created + updated_at: + type: integer + 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"] + description: The status of the message + source_id: + type: string + description: The source ID of the message content_type: type: string enum: ["text", "input_select", "cards", "form"] @@ -10,22 +42,31 @@ properties: content_attributes: type: object description: The content attributes for each content_type - message_type: + sender_type: type: string - enum: ["incoming", "outgoing", "activity", "template"] - description: The type of the message - created_at: - type: integer - description: The time at which message was created - private: - type: boolean - description: The flags which shows whether the message is private or not + enum: ["contact", "agent", "agent_bot"] + description: The type of the sender + sender_id: + type: number + description: The ID of the sender + external_source_ids: + type: object + description: The external source IDs of the message + additional_attributes: + type: object + description: The additional attributes of the message + processed_message_content: + type: string + description: The processed message content + sentiment: + type: object + description: The sentiment of the message + conversation: + type: object + description: The conversation object attachment: type: object description: The file object attached to the image sender: type: object description: User/Agent/AgentBot object - conversation_id: - type: number - description: ID of the conversation diff --git a/swagger/definitions/resource/message_detailed.yml b/swagger/definitions/resource/message_detailed.yml new file mode 100644 index 000000000..49ff7aa09 --- /dev/null +++ b/swagger/definitions/resource/message_detailed.yml @@ -0,0 +1,47 @@ +type: object +properties: + id: + type: number + description: The ID of the message + content: + type: string + description: The text content of the message + inbox_id: + type: number + description: The ID of the inbox + conversation_id: + type: number + description: The ID of the conversation + message_type: + type: integer + enum: [0, 1, 2, 3] + 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"] + description: The type of the message content + status: + type: string + enum: ["sent", "delivered", "read", "failed"] + description: The status of the message + content_attributes: + type: object + description: The content attributes for each content_type + properties: + in_reply_to: + type: string + description: ID of the message this is replying to + nullable: true + created_at: + type: integer + description: The timestamp when message was created + private: + type: boolean + description: The flag which shows whether the message is private or not + source_id: + type: string + 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) \ No newline at end of file diff --git a/swagger/definitions/resource/portal.yml b/swagger/definitions/resource/portal.yml index a1bef33bc..5268e48d5 100644 --- a/swagger/definitions/resource/portal.yml +++ b/swagger/definitions/resource/portal.yml @@ -1,34 +1,6 @@ type: object properties: - id: - type: integer - archived: - type: boolean - color: - type: string - config: - type: object - description: Save information about locales, allowed_locales and default portal/help-center locale - custom_domain: - type: string - header_text: - type: string - description: The text content. - homepage_link: - type: string - name: - type: string - slug: - type: string - page_title: - type: string - account_id: - type: integer - categories: + payload: type: array items: - $ref: '#/definitions/category' - articles: - type: array - items: - $ref: '#/definitions/article' + $ref: '#/components/schemas/portal_item' diff --git a/swagger/definitions/resource/portal_config.yml b/swagger/definitions/resource/portal_config.yml new file mode 100644 index 000000000..176c2ffc8 --- /dev/null +++ b/swagger/definitions/resource/portal_config.yml @@ -0,0 +1,18 @@ +type: object +description: Configuration settings for the portal +properties: + allowed_locales: + type: array + description: List of allowed locales for the portal + items: + type: object + properties: + code: + type: string + description: The language code + articles_count: + type: integer + description: Number of articles in this locale + categories_count: + type: integer + description: Number of categories in this locale \ No newline at end of file diff --git a/swagger/definitions/resource/portal_item.yml b/swagger/definitions/resource/portal_item.yml new file mode 100644 index 000000000..461a64f3a --- /dev/null +++ b/swagger/definitions/resource/portal_item.yml @@ -0,0 +1,40 @@ +type: object +properties: + id: + type: integer + description: The ID of the portal + archived: + type: boolean + description: Whether the portal is archived + color: + type: string + description: The color code for the portal + config: + $ref: '#/components/schemas/portal_config' + custom_domain: + type: string + description: Custom domain for the portal + header_text: + type: string + description: The header text for the portal + homepage_link: + type: string + description: Homepage link for the portal + name: + type: string + description: Name of the portal + slug: + type: string + description: URL slug for the portal + page_title: + type: string + description: Page title for the portal + account_id: + type: integer + description: ID of the account the portal belongs to + inbox: + $ref: '#/components/schemas/inbox' + logo: + $ref: '#/components/schemas/portal_logo' + meta: + $ref: '#/components/schemas/portal_meta' \ No newline at end of file diff --git a/swagger/definitions/resource/portal_logo.yml b/swagger/definitions/resource/portal_logo.yml new file mode 100644 index 000000000..606d655c7 --- /dev/null +++ b/swagger/definitions/resource/portal_logo.yml @@ -0,0 +1,23 @@ +type: object +properties: + id: + type: integer + description: ID of the logo file + portal_id: + type: integer + description: ID of the portal this logo belongs to + file_type: + type: string + description: MIME type of the file + account_id: + type: integer + description: ID of the account + file_url: + type: string + description: URL to access the logo file + blob_id: + type: integer + description: ID of the blob + filename: + type: string + description: Name of the file \ No newline at end of file diff --git a/swagger/definitions/resource/portal_meta.yml b/swagger/definitions/resource/portal_meta.yml new file mode 100644 index 000000000..64b44fc99 --- /dev/null +++ b/swagger/definitions/resource/portal_meta.yml @@ -0,0 +1,23 @@ +type: object +properties: + all_articles_count: + type: integer + description: Total number of articles + archived_articles_count: + type: integer + nullable: true + description: Number of archived articles + published_count: + type: integer + nullable: true + description: Number of published articles + draft_articles_count: + type: integer + nullable: true + description: Number of draft articles + categories_count: + type: integer + description: Number of categories + default_locale: + type: string + description: Default locale for the portal \ No newline at end of file diff --git a/swagger/definitions/resource/portal_single.yml b/swagger/definitions/resource/portal_single.yml new file mode 100644 index 000000000..2ca8c6e15 --- /dev/null +++ b/swagger/definitions/resource/portal_single.yml @@ -0,0 +1,5 @@ +type: object +properties: + payload: + description: A single portal object (for show/update endpoints) + $ref: '#/components/schemas/portal_item' \ No newline at end of file diff --git a/swagger/definitions/resource/public/contact.yml b/swagger/definitions/resource/public/contact.yml index ae2eae8d3..453ac5c97 100644 --- a/swagger/definitions/resource/public/contact.yml +++ b/swagger/definitions/resource/public/contact.yml @@ -1,5 +1,5 @@ type: object -properties: +properties: id: type: integer description: Id of the contact @@ -14,4 +14,4 @@ properties: description: Email of the contact pubsub_token: type: string - description: The token to be used to connect to chatwoot websocket \ No newline at end of file + description: The token to be used to connect to chatwoot websocket diff --git a/swagger/definitions/resource/public/conversation.yml b/swagger/definitions/resource/public/conversation.yml index a2f797e1b..67febca2d 100644 --- a/swagger/definitions/resource/public/conversation.yml +++ b/swagger/definitions/resource/public/conversation.yml @@ -9,7 +9,7 @@ properties: messages: type: array items: - $ref: '#/definitions/message' + $ref: '#/components/schemas/message' description: Messages in the conversation contact: type: object diff --git a/swagger/definitions/resource/user.yml b/swagger/definitions/resource/user.yml index c06cbab8e..329dba12e 100644 --- a/swagger/definitions/resource/user.yml +++ b/swagger/definitions/resource/user.yml @@ -2,27 +2,77 @@ type: object properties: id: type: number - uid: - type: string - name: - type: string - available_name: - type: string - display_name: - type: string - email: + access_token: type: string account_id: type: number + available_name: + type: string + avatar_url: + type: string + confirmed: + type: boolean + display_name: + type: string + nullable: true + message_signature: + type: string + nullable: true + email: + type: string + hmac_identifier: + type: string + inviter_id: + type: number + name: + type: string + provider: + type: string + pubsub_token: + type: string role: type: string enum: ['agent', 'administrator'] - confirmed: - type: boolean - custom_attributes: + ui_settings: + type: object + uid: + type: string + type: + type: string + nullable: true + custom_attributes: type: object description: Available for users who are created through platform APIs and has custom attributes associated. accounts: type: array items: - $ref: '#/definitions/account' + type: object + properties: + id: + type: number + name: + type: string + status: + type: string + active_at: + type: string + format: date-time + role: + type: string + enum: ['administrator', 'agent'] + permissions: + type: array + items: + type: string + availability: + type: string + availability_status: + type: string + auto_offline: + type: boolean + custom_role_id: + type: number + nullable: true + custom_role: + type: object + nullable: true diff --git a/swagger/index.yml b/swagger/index.yml index 41f78dbc4..e27425d41 100644 --- a/swagger/index.yml +++ b/swagger/index.yml @@ -1,48 +1,94 @@ -swagger: "2.0" +openapi: '3.0.4' info: - description: This is the API documentation for Chatwoot server. - version: 1.0.0 title: Chatwoot - termsOfService: https://www.chatwoot.com/terms-of-service/ + description: This is the API documentation for Chatwoot server. + version: 1.1.0 + termsOfService: https://www.chatwoot.com/terms-of-service/ contact: email: hello@chatwoot.com license: name: MIT License url: https://opensource.org/licenses/MIT -host: app.chatwoot.com -basePath: / -schemes: - - https -produces: -- application/json; charset=utf-8 -consumes: -- application/json; charset=utf-8 -securityDefinitions: - userApiKey: - type: apiKey - in: header - name: api_access_token - description: This token can be obtained by visiting the profile page or via rails console. Provides access to endpoints based on the user permissions levels. This token can be saved by an external system when user is created via API, to perform activities on behalf of the user. - agentBotApiKey: - type: apiKey - in: header - name: api_access_token - description: This token should be provided by system admin or obtained via rails console. This token can be used to build bot integrations and can only access limited apis. - platformAppApiKey: - type: apiKey - in: header - name: api_access_token - description: This token can be obtained by the system admin after creating a platformApp. This token should be used to provision agent bots, accounts, users and their roles. -security: - - userApiKey: [] - +servers: + - url: https://app.chatwoot.com/ paths: $ref: ./paths/index.yml -definitions: - $ref: ./definitions/index.yml -parameters: - $ref: ./parameters/index.yml - +components: + schemas: + $ref: ./definitions/index.yml + parameters: + $ref: ./parameters/index.yml + securitySchemes: + userApiKey: + type: apiKey + in: header + name: api_access_token + description: This token can be obtained by visiting the profile page or via rails console. Provides access to endpoints based on the user permissions levels. This token can be saved by an external system when user is created via API, to perform activities on behalf of the user. + agentBotApiKey: + type: apiKey + in: header + name: api_access_token + description: This token should be provided by system admin or obtained via rails console. This token can be used to build bot integrations and can only access limited apis. + platformAppApiKey: + type: apiKey + in: header + name: api_access_token + description: This token can be obtained by the system admin after creating a platformApp. This token should be used to provision agent bots, accounts, users and their roles. +tags: + - name: Accounts + description: Account management APIs + - name: Account Users + description: Account user management APIs + - name: AgentBots + description: Bot integrations + - name: Users + description: User management APIs + - name: Account AgentBots + description: Account-specific Agent Bots + - name: Agents + description: Agent management APIs + - name: Canned Responses + description: Pre-defined responses for common queries + - name: Contacts + description: Contact management APIs + - name: Contact Labels + description: Manage contact labels + - name: Conversation Assignments + description: Manage conversation assignments + - name: Conversation Labels + description: Manage conversation labels + - name: Conversations + description: Conversation management APIs + - name: Custom Attributes + description: Custom fields for contacts and conversations + - name: Custom Filters + description: Saved filters for conversations + - name: Inboxes + description: Communication channels setup + - name: Integrations + description: Third-party integrations + - name: Messages + description: Message management APIs + - name: Profile + description: User profile APIs + - name: Reports + description: Analytics and reporting APIs + - name: Teams + description: Team management APIs + - name: Webhooks + description: Event notification webhooks + - name: Automation Rule + description: Workflow automation rules + - name: Help Center + description: Knowledge base management + - name: Contacts API + description: Public contact APIs + - name: Conversations API + description: Public conversation APIs + - name: Messages API + description: Public message APIs + - name: CSAT Survey Page + description: Customer satisfaction survey x-tagGroups: - name: Platform tags: @@ -57,7 +103,7 @@ x-tagGroups: - Canned Responses - Contacts - Contact Labels - - Conversation Assignment + - Conversation Assignments - Conversation Labels - Conversations - Custom Attributes diff --git a/swagger/parameters/account_id.yml b/swagger/parameters/account_id.yml index aa3d48a8a..133155c21 100644 --- a/swagger/parameters/account_id.yml +++ b/swagger/parameters/account_id.yml @@ -1,5 +1,6 @@ in: path name: account_id -type: integer +schema: + type: integer required: true description: The numeric ID of the account diff --git a/swagger/parameters/agent_bot_id.yml b/swagger/parameters/agent_bot_id.yml index 6c94ef5dc..4d1479cb4 100644 --- a/swagger/parameters/agent_bot_id.yml +++ b/swagger/parameters/agent_bot_id.yml @@ -1,5 +1,6 @@ in: path name: id -type: integer +schema: + type: integer required: true description: The ID of the agentbot to be updated diff --git a/swagger/parameters/contact_sort.yml b/swagger/parameters/contact_sort.yml index 6f3da0b1b..f25d07760 100644 --- a/swagger/parameters/contact_sort.yml +++ b/swagger/parameters/contact_sort.yml @@ -1,14 +1,15 @@ in: query name: sort -type: string -enum: - - name - - email - - phone_number - - last_activity_at - - -name - - -email - - -phone_number - - -last_activity_at +schema: + type: string + enum: + - name + - email + - phone_number + - last_activity_at + - -name + - -email + - -phone_number + - -last_activity_at required: false description: The attribute by which list should be sorted diff --git a/swagger/parameters/conversation_id.yml b/swagger/parameters/conversation_id.yml index d8de755fe..4f4cb19ab 100644 --- a/swagger/parameters/conversation_id.yml +++ b/swagger/parameters/conversation_id.yml @@ -1,5 +1,6 @@ in: path name: conversation_id -type: integer +schema: + type: integer required: true description: The numeric ID of the conversation diff --git a/swagger/parameters/conversation_uuid.yml b/swagger/parameters/conversation_uuid.yml index b2bff7f39..c585ddc09 100644 --- a/swagger/parameters/conversation_uuid.yml +++ b/swagger/parameters/conversation_uuid.yml @@ -1,5 +1,6 @@ in: path name: conversation_uuid -type: integer +schema: + type: integer required: true description: The uuid of the conversation diff --git a/swagger/parameters/custom_filter_id.yml b/swagger/parameters/custom_filter_id.yml index b19f52f8e..46776511f 100644 --- a/swagger/parameters/custom_filter_id.yml +++ b/swagger/parameters/custom_filter_id.yml @@ -1,5 +1,6 @@ in: path name: custom_filter_id -type: integer +schema: + type: integer required: true description: The numeric ID of the custom filter diff --git a/swagger/parameters/hook_id.yml b/swagger/parameters/hook_id.yml index c1600def4..f1c50c3d4 100644 --- a/swagger/parameters/hook_id.yml +++ b/swagger/parameters/hook_id.yml @@ -1,5 +1,6 @@ in: path name: hook_id -type: integer +schema: + type: integer required: true description: The numeric ID of the integration hook diff --git a/swagger/parameters/inbox_id.yml b/swagger/parameters/inbox_id.yml index 716d34de6..58640fb08 100644 --- a/swagger/parameters/inbox_id.yml +++ b/swagger/parameters/inbox_id.yml @@ -1,5 +1,6 @@ in: path name: inbox_id -type: integer +schema: + type: integer required: true description: The ID of the Inbox diff --git a/swagger/parameters/message_id.yml b/swagger/parameters/message_id.yml index 3fc6edfcf..3064a31e2 100644 --- a/swagger/parameters/message_id.yml +++ b/swagger/parameters/message_id.yml @@ -1,5 +1,6 @@ in: path name: message_id -type: integer +schema: + type: integer required: true description: The numeric ID of the message diff --git a/swagger/parameters/page.yml b/swagger/parameters/page.yml index 8cdc77b1e..3ef0f55b6 100644 --- a/swagger/parameters/page.yml +++ b/swagger/parameters/page.yml @@ -1,6 +1,7 @@ in: query name: page -type: integer -default: 1 +schema: + type: integer + default: 1 required: false description: The page parameter diff --git a/swagger/parameters/platform_user_id.yml b/swagger/parameters/platform_user_id.yml index d31f71878..708a0aad9 100644 --- a/swagger/parameters/platform_user_id.yml +++ b/swagger/parameters/platform_user_id.yml @@ -1,5 +1,6 @@ in: path name: id -type: integer +schema: + type: integer required: true description: The numeric ID of the user on the platform diff --git a/swagger/parameters/portal_id.yml b/swagger/parameters/portal_id.yml index b45f69629..dcd1f8b32 100644 --- a/swagger/parameters/portal_id.yml +++ b/swagger/parameters/portal_id.yml @@ -1,5 +1,6 @@ in: path -name: portal_id -type: integer +name: id +schema: + type: string required: true -description: The numeric ID of the portal +description: The slug identifier of the portal diff --git a/swagger/parameters/public/contact_identifier.yml b/swagger/parameters/public/contact_identifier.yml index 6bb522c6b..3dd2d28c2 100644 --- a/swagger/parameters/public/contact_identifier.yml +++ b/swagger/parameters/public/contact_identifier.yml @@ -1,5 +1,6 @@ in: path name: contact_identifier -type: string +schema: + type: string required: true description: The source id of contact obtained on contact create diff --git a/swagger/parameters/public/inbox_identifier.yml b/swagger/parameters/public/inbox_identifier.yml index 091c0d73e..35e33e6c2 100644 --- a/swagger/parameters/public/inbox_identifier.yml +++ b/swagger/parameters/public/inbox_identifier.yml @@ -1,5 +1,6 @@ in: path name: inbox_identifier -type: string +schema: + type: string required: true description: The identifier obtained from API inbox channel diff --git a/swagger/parameters/report_metric.yml b/swagger/parameters/report_metric.yml index 552b0d310..c4bb6cc76 100644 --- a/swagger/parameters/report_metric.yml +++ b/swagger/parameters/report_metric.yml @@ -1,12 +1,13 @@ in: query name: metric -type: string -enum: - - conversations_count - - incoming_messages_count - - outgoing_messages_count - - avg_first_response_time - - avg_resolution_time - - resolutions_count +schema: + type: string + enum: + - conversations_count + - incoming_messages_count + - outgoing_messages_count + - avg_first_response_time + - avg_resolution_time + - resolutions_count required: true description: The type of metric diff --git a/swagger/parameters/report_type.yml b/swagger/parameters/report_type.yml index 9f5fbf363..668fd0922 100644 --- a/swagger/parameters/report_type.yml +++ b/swagger/parameters/report_type.yml @@ -1,11 +1,12 @@ in: query name: type -type: string -enum: - - account - - agent - - inbox - - label - - team +schema: + type: string + enum: + - account + - agent + - inbox + - label + - team required: true description: Type of report diff --git a/swagger/parameters/source_id.yml b/swagger/parameters/source_id.yml index 408beed86..a2c3bb807 100644 --- a/swagger/parameters/source_id.yml +++ b/swagger/parameters/source_id.yml @@ -1,5 +1,6 @@ in: path name: source_id required: true -type: string +schema: + type: string description: "Id of the session for which the conversation is created.\n\n\n\n Source Ids can be obtained through contactable inboxes API or via generated.Website: Chatwoot generated string which can be obtained from webhook events. Phone Channels(Twilio): Phone number in e164 format Email Channels: Contact Email address API Channel: Any Random String" diff --git a/swagger/parameters/team_id.yml b/swagger/parameters/team_id.yml index c18532b75..9caca20b3 100644 --- a/swagger/parameters/team_id.yml +++ b/swagger/parameters/team_id.yml @@ -1,5 +1,6 @@ in: path name: team_id -type: integer +schema: + type: integer required: true description: The ID of the team to be updated diff --git a/swagger/parameters/webhook_id.yml b/swagger/parameters/webhook_id.yml index 4400a082c..524a3e733 100644 --- a/swagger/parameters/webhook_id.yml +++ b/swagger/parameters/webhook_id.yml @@ -1,5 +1,6 @@ in: path name: webhook_id -type: integer +schema: + type: integer required: true description: The numeric ID of the webhook diff --git a/swagger/paths/application/agent_bots/create.yml b/swagger/paths/application/agent_bots/create.yml index e0fde6184..bc97acb32 100644 --- a/swagger/paths/application/agent_bots/create.yml +++ b/swagger/paths/application/agent_bots/create.yml @@ -3,17 +3,24 @@ tags: operationId: create-an-account-agent-bot summary: Create an Agent Bot description: Create an agent bot in the account -parameters: - - $ref: '#/parameters/account_id' - - name: data - in: body - required: true - schema: - $ref: '#/definitions/agent_bot_create_update_payload' +security: + - userApiKey: [] +requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/agent_bot_create_update_payload' responses: - 200: + '200': description: Success - schema: - $ref: '#/definitions/agent_bot' - 401: + content: + application/json: + schema: + $ref: '#/components/schemas/agent_bot' + '401': description: Unauthorized + content: + application/json: + schema: + $ref: '#/components/schemas/bad_request_error' diff --git a/swagger/paths/application/agent_bots/delete.yml b/swagger/paths/application/agent_bots/delete.yml index 0e3daf6cb..84dcc6f55 100644 --- a/swagger/paths/application/agent_bots/delete.yml +++ b/swagger/paths/application/agent_bots/delete.yml @@ -3,10 +3,20 @@ tags: operationId: delete-an-account-agent-bot summary: Delete an AgentBot description: Delete an AgentBot from the account +security: + - userApiKey: [] responses: 200: description: Success 401: description: Unauthorized + content: + application/json: + schema: + $ref: '#/components/schemas/bad_request_error' 404: description: The agent bot does not exist in the account + content: + application/json: + schema: + $ref: '#/components/schemas/bad_request_error' diff --git a/swagger/paths/application/agent_bots/index.yml b/swagger/paths/application/agent_bots/index.yml index 2c5fe289f..20b0bab82 100644 --- a/swagger/paths/application/agent_bots/index.yml +++ b/swagger/paths/application/agent_bots/index.yml @@ -3,14 +3,21 @@ tags: operationId: list-all-account-agent-bots summary: List all AgentBots description: List all agent bots available for the current account +security: + - userApiKey: [] responses: - 200: + '200': description: Success - schema: - type: array - description: 'Array of agent bots' - items: - $ref: '#/definitions/agent_bot' - - 401: + content: + application/json: + schema: + type: array + description: 'Array of agent bots' + items: + $ref: '#/components/schemas/agent_bot' + '401': description: Unauthorized + content: + application/json: + schema: + $ref: '#/components/schemas/bad_request_error' diff --git a/swagger/paths/application/agent_bots/show.yml b/swagger/paths/application/agent_bots/show.yml index b94637690..061908c2c 100644 --- a/swagger/paths/application/agent_bots/show.yml +++ b/swagger/paths/application/agent_bots/show.yml @@ -3,12 +3,24 @@ tags: operationId: get-details-of-a-single-account-agent-bot summary: Get an agent bot details description: Get the details of an agent bot in the account +security: + - userApiKey: [] responses: - 200: + '200': description: Success - schema: - $ref: '#/definitions/agent_bot' - 401: + content: + application/json: + schema: + $ref: '#/components/schemas/agent_bot' + '401': description: Unauthorized - 404: + content: + application/json: + schema: + $ref: '#/components/schemas/bad_request_error' + '404': description: The given agent bot ID does not exist in the account + content: + application/json: + schema: + $ref: '#/components/schemas/bad_request_error' diff --git a/swagger/paths/application/agent_bots/update.yml b/swagger/paths/application/agent_bots/update.yml index 562d58841..0f988cb18 100644 --- a/swagger/paths/application/agent_bots/update.yml +++ b/swagger/paths/application/agent_bots/update.yml @@ -3,16 +3,24 @@ tags: operationId: update-an-account-agent-bot summary: Update an agent bot description: Update an agent bot's attributes -parameters: - - name: data - in: body - required: true - schema: - $ref: '#/definitions/agent_bot_create_update_payload' +security: + - userApiKey: [] +requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/agent_bot_create_update_payload' responses: - 200: + '200': description: Success - schema: - $ref: '#/definitions/agent_bot' - 401: + content: + application/json: + schema: + $ref: '#/components/schemas/agent_bot' + '401': description: Unauthorized + content: + application/json: + schema: + $ref: '#/components/schemas/bad_request_error' diff --git a/swagger/paths/application/agents/create.yml b/swagger/paths/application/agents/create.yml index dd191d81f..51436381e 100644 --- a/swagger/paths/application/agents/create.yml +++ b/swagger/paths/application/agents/create.yml @@ -5,39 +5,23 @@ summary: Add a New Agent description: Add a new Agent to Account security: - userApiKey: [] -parameters: - - name: data - in: body - required: true - schema: - type: object - required: - - name - - email - - role - properties: - name: - type: string - description: Full Name of the agent - email: - type: string - description: Email of the Agent - role: - type: string - enum: ['agent', 'administrator'] - description: Whether its administrator or agent - availability_status: - type: string - enum: ['available', 'busy', 'offline'] - description: The availability setting of the agent. - auto_offline: - type: boolean - description: Whether the availability status of agent is configured to go offline automatically when away. +requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/agent_create_payload' responses: - 200: + '200': description: Success - schema: - description: 'Newly Created Agent' - $ref: '#/definitions/agent' - 403: + content: + application/json: + schema: + description: 'Newly Created Agent' + $ref: '#/components/schemas/agent' + '403': description: Access denied + content: + application/json: + schema: + $ref: '#/components/schemas/bad_request_error' diff --git a/swagger/paths/application/agents/delete.yml b/swagger/paths/application/agents/delete.yml index 118ddf234..d006659e3 100644 --- a/swagger/paths/application/agents/delete.yml +++ b/swagger/paths/application/agents/delete.yml @@ -8,13 +8,22 @@ security: parameters: - in: path name: id - type: integer + schema: + type: integer required: true - description: The ID of the agent to be deleted + description: The ID of the agent to be deleted. responses: 200: description: Success 404: description: Agent 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' diff --git a/swagger/paths/application/agents/index.yml b/swagger/paths/application/agents/index.yml index 7961ad3b0..2073e2ba0 100644 --- a/swagger/paths/application/agents/index.yml +++ b/swagger/paths/application/agents/index.yml @@ -8,10 +8,16 @@ security: responses: 200: description: Success - schema: - type: array - description: 'Array of all active agents' - items: - $ref: '#/definitions/agent' + content: + application/json: + schema: + type: array + description: 'Array of all active agents' + items: + $ref: '#/components/schemas/agent' 403: description: Access denied + content: + application/json: + schema: + $ref: '#/components/schemas/bad_request_error' diff --git a/swagger/paths/application/agents/update.yml b/swagger/paths/application/agents/update.yml index a72f5ed4e..afb5579f3 100644 --- a/swagger/paths/application/agents/update.yml +++ b/swagger/paths/application/agents/update.yml @@ -8,35 +8,33 @@ security: parameters: - in: path name: id - type: integer + schema: + type: integer required: true description: The ID of the agent to be updated. - - name: data - in: body - required: true - schema: - type: object - required: - - role - properties: - role: - type: string - enum: ['agent', 'administrator'] - description: Whether its administrator or agent - availability: - type: string - enum: ['available', 'busy', 'offline'] - description: The availability setting of the agent. - auto_offline: - type: boolean - description: Whether the availability status of agent is configured to go offline automatically when away. +requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/agent_update_payload' responses: 200: description: Success - schema: - description: 'The updated agent' - $ref: '#/definitions/agent' + content: + application/json: + schema: + description: 'The updated agent' + $ref: '#/components/schemas/agent' 404: description: Agent 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' diff --git a/swagger/paths/application/article/create.yml b/swagger/paths/application/article/create.yml index 0733fb860..02b13b6c1 100644 --- a/swagger/paths/application/article/create.yml +++ b/swagger/paths/application/article/create.yml @@ -5,16 +5,22 @@ summary: Add a new article description: Add a new article to portal security: - userApiKey: [] -parameters: - - name: data - in: body - required: true - schema: - $ref: '#/definitions/article_create_update_payload' +requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/article_create_update_payload' responses: '200': description: Success - schema: - $ref: '#/definitions/article' + content: + application/json: + schema: + $ref: '#/components/schemas/article' '403': description: Access denied + content: + application/json: + schema: + $ref: '#/components/schemas/bad_request_error' diff --git a/swagger/paths/application/automation_rule/create.yml b/swagger/paths/application/automation_rule/create.yml index ac5de803a..a8a309dad 100644 --- a/swagger/paths/application/automation_rule/create.yml +++ b/swagger/paths/application/automation_rule/create.yml @@ -5,16 +5,22 @@ summary: Add a new automation rule description: Add a new automation rule to account security: - userApiKey: [] -parameters: - - name: data - in: body - required: true - schema: - $ref: '#/definitions/automation_rule_create_update_payload' +requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/automation_rule_create_update_payload' responses: '200': description: Success - schema: - $ref: '#/definitions/automation_rule' + content: + application/json: + schema: + $ref: '#/components/schemas/automation_rule' '403': description: Access denied + content: + application/json: + schema: + $ref: '#/components/schemas/bad_request_error' diff --git a/swagger/paths/application/automation_rule/delete.yml b/swagger/paths/application/automation_rule/delete.yml index f82647d1d..4d30fb812 100644 --- a/swagger/paths/application/automation_rule/delete.yml +++ b/swagger/paths/application/automation_rule/delete.yml @@ -8,7 +8,8 @@ security: parameters: - in: path name: id - type: integer + schema: + type: integer required: true description: The ID of the automation rule to be deleted responses: @@ -16,5 +17,13 @@ responses: description: Success '403': description: Access denied + content: + application/json: + schema: + $ref: '#/components/schemas/bad_request_error' '404': description: automation rule not found + content: + application/json: + schema: + $ref: '#/components/schemas/bad_request_error' diff --git a/swagger/paths/application/automation_rule/index.yml b/swagger/paths/application/automation_rule/index.yml index 031696aeb..b5193e1af 100644 --- a/swagger/paths/application/automation_rule/index.yml +++ b/swagger/paths/application/automation_rule/index.yml @@ -3,18 +3,21 @@ tags: operationId: get-account-automation-rule summary: List all automation rules in an account parameters: - - $ref: '#/parameters/account_id' - - $ref: '#/parameters/page' + - $ref: '#/components/parameters/account_id' + - $ref: '#/components/parameters/page' description: Get details of automation rules in an Account security: - userApiKey: [] responses: '200': description: Success - schema: - type: array - description: Array of all automation rules - items: - $ref: '#/definitions/automation_rule' + content: + application/json: + schema: + $ref: '#/components/schemas/automation_rule' '403': description: Access denied + content: + application/json: + schema: + $ref: '#/components/schemas/bad_request_error' diff --git a/swagger/paths/application/automation_rule/show.yml b/swagger/paths/application/automation_rule/show.yml index d99f8170b..ea3db048b 100644 --- a/swagger/paths/application/automation_rule/show.yml +++ b/swagger/paths/application/automation_rule/show.yml @@ -3,18 +3,51 @@ tags: operationId: get-details-of-a-single-automation-rule summary: Get a automation rule details description: Get the details of a automation rule in the account +security: + - userApiKey: [] parameters: - in: path name: id - type: integer + schema: + type: integer required: true description: The ID of the automation rule to be updated. responses: '200': description: Success - schema: - $ref: '#/definitions/automation_rule' + content: + application/json: + schema: + $ref: '#/components/schemas/automation_rule' + example: + payload: + id: 90 + account_id: 1 + name: "add-label-bug-if-message-contains-bug" + description: "add-label-bug-if-message-contains-bug" + event_name: "message_created" + conditions: + - values: ["incoming"] + attribute_key: "message_type" + query_operator: "and" + filter_operator: "equal_to" + - values: ["bug"] + attribute_key: "content" + filter_operator: "contains" + actions: + - action_name: "add_label" + action_params: ["bugs", "support-query"] + created_on: 1650555440 + active: true '401': description: Unauthorized + content: + application/json: + schema: + $ref: '#/components/schemas/bad_request_error' '404': description: The given rule ID does not exist in the account + content: + application/json: + schema: + $ref: '#/components/schemas/bad_request_error' diff --git a/swagger/paths/application/automation_rule/update.yml b/swagger/paths/application/automation_rule/update.yml index 35a4cb67a..414885799 100644 --- a/swagger/paths/application/automation_rule/update.yml +++ b/swagger/paths/application/automation_rule/update.yml @@ -8,20 +8,32 @@ security: parameters: - in: path name: id - type: integer + schema: + type: integer required: true description: The ID of the automation rule to be updated. - - name: data - in: body - required: true - schema: - $ref: '#/definitions/automation_rule_create_update_payload' +requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/automation_rule_create_update_payload' responses: '200': description: Success - schema: - $ref: '#/definitions/automation_rule' + content: + application/json: + schema: + $ref: '#/components/schemas/automation_rule' '403': description: Access denied + content: + application/json: + schema: + $ref: '#/components/schemas/bad_request_error' '404': description: Rule not found + content: + application/json: + schema: + $ref: '#/components/schemas/bad_request_error' diff --git a/swagger/paths/application/canned_responses/create.yml b/swagger/paths/application/canned_responses/create.yml index 95eacafb1..95d822a87 100644 --- a/swagger/paths/application/canned_responses/create.yml +++ b/swagger/paths/application/canned_responses/create.yml @@ -5,17 +5,23 @@ summary: Add a New Canned Response description: Add a new Canned Response to Account security: - userApiKey: [] -parameters: - - name: data - in: body - required: true - schema: - $ref: '#/definitions/canned_response_create_update_payload' +requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/canned_response_create_update_payload' responses: - 200: + '200': description: Success - schema: - description: 'Newly Created Canned Response' - $ref: '#/definitions/canned_response' - 403: + content: + application/json: + schema: + description: 'Newly Created Canned Response' + $ref: '#/components/schemas/canned_response' + '403': description: Access denied + content: + application/json: + schema: + $ref: '#/components/schemas/bad_request_error' diff --git a/swagger/paths/application/canned_responses/delete.yml b/swagger/paths/application/canned_responses/delete.yml index 2b1d08238..57ff1850b 100644 --- a/swagger/paths/application/canned_responses/delete.yml +++ b/swagger/paths/application/canned_responses/delete.yml @@ -8,13 +8,22 @@ security: parameters: - in: path name: id - type: integer + schema: + type: integer required: true description: The ID of the canned response to be deleted responses: - 200: + '200': description: Success - 404: + '404': description: Canned Response not found - 403: + content: + application/json: + schema: + $ref: '#/components/schemas/bad_request_error' + '403': description: Access denied + content: + application/json: + schema: + $ref: '#/components/schemas/bad_request_error' diff --git a/swagger/paths/application/canned_responses/index.yml b/swagger/paths/application/canned_responses/index.yml index 3094a23a6..1b8ad694c 100644 --- a/swagger/paths/application/canned_responses/index.yml +++ b/swagger/paths/application/canned_responses/index.yml @@ -6,12 +6,18 @@ description: Get Details of Canned Responses in an Account security: - userApiKey: [] responses: - 200: + '200': description: Success - schema: - type: array - description: 'Array of all canned responses' - items: - $ref: '#/definitions/canned_response' - 403: + content: + application/json: + schema: + type: array + description: 'Array of all canned responses' + items: + $ref: '#/components/schemas/canned_response' + '403': description: Access denied + content: + application/json: + schema: + $ref: '#/components/schemas/bad_request_error' diff --git a/swagger/paths/application/canned_responses/update.yml b/swagger/paths/application/canned_responses/update.yml index 7ea801f2f..57315d5be 100644 --- a/swagger/paths/application/canned_responses/update.yml +++ b/swagger/paths/application/canned_responses/update.yml @@ -1,5 +1,5 @@ tags: - - Canned Response + - Canned Responses operationId: update-canned-response-in-account summary: Update Canned Response in Account description: Update a Canned Response in Account @@ -8,21 +8,33 @@ security: parameters: - in: path name: id - type: integer + schema: + type: integer required: true description: The ID of the canned response to be updated. - - name: data - in: body - required: true - schema: - $ref: '#/definitions/canned_response_create_update_payload' +requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/canned_response_create_update_payload' responses: - 200: + '200': description: Success - schema: - description: 'The updated canned response' - $ref: '#/definitions/canned_response' - 404: + content: + application/json: + schema: + description: 'The updated canned response' + $ref: '#/components/schemas/canned_response' + '404': description: Agent not found - 403: + content: + application/json: + schema: + $ref: '#/components/schemas/bad_request_error' + '403': description: Access denied + content: + application/json: + schema: + $ref: '#/components/schemas/bad_request_error' diff --git a/swagger/paths/application/category/create.yml b/swagger/paths/application/category/create.yml index 7b569a40b..56c757517 100644 --- a/swagger/paths/application/category/create.yml +++ b/swagger/paths/application/category/create.yml @@ -5,16 +5,22 @@ summary: Add a new category description: Add a new category to portal security: - userApiKey: [] -parameters: - - name: data - in: body - required: true - schema: - $ref: '#/definitions/category_create_update_payload' +requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/category_create_update_payload' responses: '200': description: Success - schema: - $ref: '#/definitions/category' + content: + application/json: + schema: + $ref: '#/components/schemas/category' '403': description: Access denied + content: + application/json: + schema: + $ref: '#/components/schemas/bad_request_error' diff --git a/swagger/paths/application/contact_inboxes/create.yml b/swagger/paths/application/contact_inboxes/create.yml index 2b784b5dd..64ebeb21c 100644 --- a/swagger/paths/application/contact_inboxes/create.yml +++ b/swagger/paths/application/contact_inboxes/create.yml @@ -5,34 +5,47 @@ post: description: Create a contact inbox record for an inbox summary: Create contact inbox parameters: - - $ref: '#/parameters/account_id' + - $ref: '#/components/parameters/account_id' - name: id in: path - type: number + schema: + type: number description: ID of the contact required: true - - name: data - in: body - required: true - schema: - type: object - required: - - inbox_id - properties: - inbox_id: - type: number - description: The ID of the inbox - source_id: - type: string - description: Contact Inbox Source Id + security: + - userApiKey: [] + requestBody: + required: true + content: + application/json: + schema: + type: object + required: + - inbox_id + properties: + inbox_id: + type: number + description: The ID of the inbox + example: 1 + source_id: + type: string + description: Contact Inbox Source Id responses: - 200: + '200': description: Success - schema: - $ref: '#/definitions/contact_inboxes' - 401: + content: + application/json: + schema: + $ref: '#/components/schemas/contact_inboxes' + '401': description: Authentication error - schema: - $ref: '#/definitions/bad_request_error' - 422: + content: + application/json: + schema: + $ref: '#/components/schemas/bad_request_error' + '422': description: Incorrect payload + content: + application/json: + schema: + $ref: '#/components/schemas/bad_request_error' diff --git a/swagger/paths/application/contactable_inboxes/get.yml b/swagger/paths/application/contactable_inboxes/get.yml index 23ce37788..357c84597 100644 --- a/swagger/paths/application/contactable_inboxes/get.yml +++ b/swagger/paths/application/contactable_inboxes/get.yml @@ -4,21 +4,32 @@ get: operationId: contactableInboxesGet description: Get List of contactable Inboxes summary: Get Contactable Inboxes + security: + - userApiKey: [] parameters: - - $ref: '#/parameters/account_id' + - $ref: '#/components/parameters/account_id' - name: id in: path - type: number + schema: + type: number description: ID of the contact required: true responses: - 200: + '200': description: Success - schema: - $ref: '#/definitions/contactable_inboxes' - 401: + content: + application/json: + schema: + $ref: '#/components/schemas/contactable_inboxes_response' + '401': description: Authentication error - schema: - $ref: '#/definitions/bad_request_error' - 422: + content: + application/json: + schema: + $ref: '#/components/schemas/bad_request_error' + '422': description: Incorrect payload + content: + application/json: + schema: + $ref: '#/components/schemas/bad_request_error' diff --git a/swagger/paths/application/contacts/conversations.yml b/swagger/paths/application/contacts/conversations.yml index d08235f5e..b1bd7e522 100644 --- a/swagger/paths/application/contacts/conversations.yml +++ b/swagger/paths/application/contacts/conversations.yml @@ -1,22 +1,43 @@ +parameters: + - $ref: '#/components/parameters/account_id' + - name: id + in: path + required: true + schema: + type: number + description: ID of the contact + get: tags: - Contacts operationId: contactConversations summary: Contact Conversations - description: Get conversations associated to that contact + description: Get conversations associated with that contact parameters: - - $ref: '#/parameters/account_id' - name: id in: path - type: number - description: ID of the contact required: true - responses: - 200: - description: Success schema: - $ref: '#/definitions/contact_conversations' - 404: + type: number + description: ID of the contact + security: + - userApiKey: [] + responses: + '200': + description: Success + content: + application/json: + schema: + $ref: '#/components/schemas/contact_conversations_response' + '404': description: Contact not found - 403: + content: + application/json: + schema: + $ref: '#/components/schemas/bad_request_error' + '403': description: Access denied + content: + application/json: + schema: + $ref: '#/components/schemas/bad_request_error' diff --git a/swagger/paths/application/contacts/crud.yml b/swagger/paths/application/contacts/crud.yml index 3c982dfe9..dcabb145d 100644 --- a/swagger/paths/application/contacts/crud.yml +++ b/swagger/paths/application/contacts/crud.yml @@ -1,58 +1,94 @@ parameters: - - $ref: '#/parameters/account_id' + - $ref: '#/components/parameters/account_id' - name: id in: path - type: number - description: ID of the contact required: true + schema: + type: number + description: ID of the contact get: tags: - Contacts operationId: contactDetails summary: Show Contact + security: + - userApiKey: [] description: Get a contact belonging to the account using ID responses: - 200: + '200': description: Success - schema: - $ref: '#/definitions/extended_contact' - 404: + content: + application/json: + schema: + $ref: '#/components/schemas/contact_show_response' + '404': description: Contact not found - 403: + content: + application/json: + schema: + $ref: '#/components/schemas/bad_request_error' + '403': description: Access denied + content: + application/json: + schema: + $ref: '#/components/schemas/bad_request_error' put: tags: - Contacts operationId: contactUpdate summary: Update Contact + security: + - userApiKey: [] description: Update a contact belonging to the account using ID - parameters: - - name: data - in: body - required: true - schema: - $ref: '#/definitions/contact_update' + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/contact_update_payload' responses: - 204: + '204': description: Success - schema: - $ref: '#/definitions/contact_base' - 404: + content: + application/json: + schema: + $ref: '#/components/schemas/contact_base' + '404': description: Contact not found - 403: + content: + application/json: + schema: + $ref: '#/components/schemas/bad_request_error' + '403': description: Access denied + content: + application/json: + schema: + $ref: '#/components/schemas/bad_request_error' delete: tags: - Contacts operationId: contactDelete summary: Delete Contact + security: + - userApiKey: [] + description: Delete a contact belonging to the account using ID responses: - 200: + '200': description: Success - 401: + '401': description: Unauthorized - 404: + content: + application/json: + schema: + $ref: '#/components/schemas/bad_request_error' + '404': description: Contact not found + content: + application/json: + schema: + $ref: '#/components/schemas/bad_request_error' diff --git a/swagger/paths/application/contacts/filter.yml b/swagger/paths/application/contacts/filter.yml index 2a1d53cf7..10d49f8c6 100644 --- a/swagger/paths/application/contacts/filter.yml +++ b/swagger/paths/application/contacts/filter.yml @@ -1,58 +1,64 @@ -tags: - - Contacts -operationId: contactFilter -description: Filter contacts with custom filter options and pagination -summary: Contact Filter -security: - - userApiKey: [] - - agentBotApiKey: [] -parameters: - - name: page - in: query - type: integer - - name: body - in: body +post: + tags: + - Contacts + operationId: contactFilter + description: Filter contacts with custom filter options and pagination + summary: Contact Filter + security: + - userApiKey: [] + parameters: + - $ref: '#/components/parameters/account_id' + - name: page + in: query + schema: + type: number + requestBody: required: true - schema: - type: object - properties: - payload: - type: array - items: - type: object - properties: - attribute_key: - type: string - description: filter attribute name - filter_operator: - type: string - description: filter operator name - enum: [ equal_to, not_equal_to, contains, does_not_contain ] - values: - type: array - items: - type: string - description: array of the attribute values to filter - query_operator: - type: string - description: query operator name - enum: [ AND, OR ] - example: - - attribute_key: 'name' - filter_operator: 'equal_to' - values: ['en'] - query_operator: 'AND' - - attribute_key: 'country_code' - filter_operator: 'equal_to' - values: ['us'] - query_operator: null - -responses: - 200: - description: Success - schema: - $ref: '#/definitions/contact_list' - 400: - description: Bad Request Error - schema: - $ref: '#/definitions/bad_request_error' + content: + application/json: + schema: + type: object + properties: + payload: + type: array + items: + type: object + properties: + attribute_key: + type: string + description: filter attribute name + filter_operator: + type: string + description: filter operator name + enum: [equal_to, not_equal_to, contains, does_not_contain] + values: + type: array + items: + type: string + description: array of the attribute values to filter + query_operator: + type: string + description: query operator name + enum: [AND, OR] + example: + - attribute_key: 'name' + filter_operator: 'equal_to' + values: ['en'] + query_operator: 'AND' + - attribute_key: 'country_code' + filter_operator: 'equal_to' + values: ['us'] + query_operator: null + responses: + '200': + description: Success + content: + application/json: + schema: + $ref: '#/components/schemas/contacts_list_response' + '400': + description: Bad Request Error + content: + application/json: + schema: + $ref: '#/components/schemas/bad_request_error' diff --git a/swagger/paths/application/contacts/labels.yml b/swagger/paths/application/contacts/labels.yml new file mode 100644 index 000000000..a764e36b2 --- /dev/null +++ b/swagger/paths/application/contacts/labels.yml @@ -0,0 +1,79 @@ +parameters: + - $ref: '#/components/parameters/account_id' + - name: id + in: path + required: true + schema: + type: number + description: ID of the contact + +get: + tags: + - Contact Labels + operationId: list-all-labels-of-a-contact + summary: List Labels + description: Lists all the labels of a contact + security: + - userApiKey: [] + responses: + '200': + description: Success + content: + application/json: + schema: + $ref: '#/components/schemas/contact_labels' + '401': + description: Unauthorized + content: + application/json: + schema: + $ref: '#/components/schemas/bad_request_error' + '404': + description: Contact not found + content: + application/json: + schema: + $ref: '#/components/schemas/bad_request_error' + +post: + tags: + - Contact Labels + operationId: contact-add-labels + summary: Add Labels + description: Add labels to a contact. Note that this API would overwrite the existing list of labels associated to the conversation. + security: + - userApiKey: [] + requestBody: + required: true + content: + application/json: + schema: + type: object + required: + - labels + properties: + labels: + type: array + description: Array of labels (comma-separated strings) + items: + type: string + example: ['support', 'billing'] + responses: + '200': + description: Success + content: + application/json: + schema: + $ref: '#/components/schemas/contact_labels' + '401': + description: Unauthorized + content: + application/json: + schema: + $ref: '#/components/schemas/bad_request_error' + '404': + description: Contact not found + content: + application/json: + schema: + $ref: '#/components/schemas/bad_request_error' diff --git a/swagger/paths/application/contacts/labels/create.yml b/swagger/paths/application/contacts/labels/create.yml deleted file mode 100644 index 01821d688..000000000 --- a/swagger/paths/application/contacts/labels/create.yml +++ /dev/null @@ -1,26 +0,0 @@ -tags: - - Contact Labels -operationId: contact-add-labels -summary: Add Labels -description: Add labels to a contact. Note that this API would overwrite the existing list of labels associated to the conversation. -parameters: - - name: data - in: body - required: true - schema: - type: object - properties: - labels: - type: array - description: Array of labels (comma-separated strings) - items: - type: string -responses: - 200: - description: Success - schema: - $ref: '#/definitions/contact_labels' - 404: - description: Contact not found - 401: - description: Unauthorized diff --git a/swagger/paths/application/contacts/labels/index.yml b/swagger/paths/application/contacts/labels/index.yml deleted file mode 100644 index ad3c7ff81..000000000 --- a/swagger/paths/application/contacts/labels/index.yml +++ /dev/null @@ -1,14 +0,0 @@ -tags: - - Contact Labels -operationId: list-all-labels-of-a-contact -summary: List Labels -description: Lists all the labels of a contact -responses: - 200: - description: Success - schema: - $ref: '#/definitions/contact_labels' - 404: - description: Contact not found - 401: - description: Unauthorized diff --git a/swagger/paths/application/contacts/list_create.yml b/swagger/paths/application/contacts/list_create.yml index 4329ed3b7..eb5754a15 100644 --- a/swagger/paths/application/contacts/list_create.yml +++ b/swagger/paths/application/contacts/list_create.yml @@ -2,21 +2,27 @@ get: tags: - Contacts operationId: contactList - description: Listing all the resolved contacts with pagination (Page size = 15) . Resolved contacts are the ones with a value for identifier, email or phone number + description: Listing all the resolved contacts with pagination (Page size = 15). Resolved contacts are the ones with a value for identifier, email or phone number summary: List Contacts + security: + - userApiKey: [] parameters: - - $ref: '#/parameters/account_id' - - $ref: '#/parameters/contact_sort_param' - - $ref: '#/parameters/page' + - $ref: '#/components/parameters/account_id' + - $ref: '#/components/parameters/contact_sort_param' + - $ref: '#/components/parameters/page' responses: - 200: + '200': description: Success - schema: - $ref: '#/definitions/contact_list' - 400: + content: + application/json: + schema: + $ref: '#/components/schemas/contacts_list_response' + '400': description: Bad Request Error - schema: - $ref: '#/definitions/bad_request_error' + content: + application/json: + schema: + $ref: '#/components/schemas/bad_request_error' post: tags: @@ -24,19 +30,26 @@ post: operationId: contactCreate description: Create a new Contact summary: Create Contact + security: + - userApiKey: [] parameters: - - $ref: '#/parameters/account_id' - - name: data - in: body - required: true - schema: - $ref: '#/definitions/contact_create' + - $ref: '#/components/parameters/account_id' + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/contact_create_payload' responses: - 200: + '200': description: Success - schema: - $ref: '#/definitions/extended_contact' - 400: + content: + application/json: + schema: + $ref: '#/components/schemas/extended_contact' + '400': description: Bad Request Error - schema: - $ref: '#/definitions/bad_request_error' + content: + application/json: + schema: + $ref: '#/components/schemas/bad_request_error' diff --git a/swagger/paths/application/contacts/search.yml b/swagger/paths/application/contacts/search.yml index 63cc11903..61075dc00 100644 --- a/swagger/paths/application/contacts/search.yml +++ b/swagger/paths/application/contacts/search.yml @@ -4,23 +4,27 @@ get: operationId: contactSearch description: Search the resolved contacts using a search key, currently supports email search (Page size = 15). Resolved contacts are the ones with a value for identifier, email or phone number summary: Search Contacts + security: + - userApiKey: [] parameters: - - $ref: '#/parameters/account_id' + - $ref: '#/components/parameters/account_id' - name: q in: query - type: string + schema: + type: string description: Search using contact `name`, `identifier`, `email` or `phone number` - - $ref: '#/parameters/contact_sort_param' - - $ref: '#/parameters/page' + - $ref: '#/components/parameters/contact_sort_param' + - $ref: '#/components/parameters/page' responses: - 200: + '200': description: Success - schema: - type: object - properties: - payload: - $ref: '#/definitions/contact_list' - 401: + content: + application/json: + schema: + $ref: '#/components/schemas/contacts_list_response' + '401': description: Authentication error - schema: - $ref: '#/definitions/bad_request_error' + content: + application/json: + schema: + $ref: '#/components/schemas/bad_request_error' diff --git a/swagger/paths/application/conversation/assignments.yml b/swagger/paths/application/conversation/assignments.yml index a67ce59ad..49b7022db 100644 --- a/swagger/paths/application/conversation/assignments.yml +++ b/swagger/paths/application/conversation/assignments.yml @@ -1,30 +1,42 @@ tags: - - Conversation Assignment + - Conversation Assignments operationId: assign-a-conversation summary: Assign Conversation description: Assign a conversation to an agent or a team security: - userApiKey: [] - agentBotApiKey: [] -parameters: - - name: data - in: body - required: true - schema: - type: object - properties: - assignee_id: - type: number - description: Id of the assignee user - team_id: - type: number - description: Id of the team. If the assignee_id is present, this param would be ignored +requestBody: + required: true + content: + application/json: + schema: + type: object + properties: + assignee_id: + type: number + description: Id of the assignee user + example: 1 + team_id: + type: number + description: Id of the team. If the assignee_id is present, this param would be ignored + example: 1 responses: - 200: + '200': description: Success - schema: - $ref: '#/definitions/user' - 404: - description: Conversation not found - 401: + content: + application/json: + schema: + $ref: '#/components/schemas/user' + '401': description: Unauthorized + content: + application/json: + schema: + $ref: '#/components/schemas/bad_request_error' + '404': + description: Conversation not found + content: + application/json: + schema: + $ref: '#/components/schemas/bad_request_error' diff --git a/swagger/paths/application/conversation/custom_attributes.yml b/swagger/paths/application/conversation/custom_attributes.yml index bfd416138..6e5786742 100644 --- a/swagger/paths/application/conversation/custom_attributes.yml +++ b/swagger/paths/application/conversation/custom_attributes.yml @@ -5,32 +5,41 @@ summary: Update Custom Attributes description: Updates the custom attributes of a conversation security: - userApiKey: [] - - agentBotApiKey: [] -parameters: - - name: data - in: body - required: true - schema: - type: object - required: - - custom_attributes - properties: - custom_attributes: - type: object - description: The custom attributes to be set for the conversation - example: - order_id: "12345" - previous_conversation: "67890" +requestBody: + required: true + content: + application/json: + schema: + type: object + required: + - custom_attributes + properties: + custom_attributes: + type: object + description: The custom attributes to be set for the conversation + example: + order_id: '12345' + previous_conversation: '67890' responses: - 200: + '200': description: Success - schema: - type: object - properties: - custom_attributes: + content: + application/json: + schema: type: object - description: The custom attributes of the conversation - 404: + properties: + custom_attributes: + type: object + description: The custom attributes of the conversation + '401': + description: Unauthorized + content: + application/json: + schema: + $ref: '#/components/schemas/bad_request_error' + '404': description: Conversation not found - 401: - description: Unauthorized \ No newline at end of file + content: + application/json: + schema: + $ref: '#/components/schemas/bad_request_error' diff --git a/swagger/paths/application/conversation/filter.yml b/swagger/paths/application/conversation/filter.yml index 4ee5f5d54..fdb318f6a 100644 --- a/swagger/paths/application/conversation/filter.yml +++ b/swagger/paths/application/conversation/filter.yml @@ -5,54 +5,59 @@ description: Filter conversations with custom filter options and pagination summary: Conversations Filter security: - userApiKey: [] - - agentBotApiKey: [] parameters: - name: page in: query - type: integer - - name: body - in: body - required: true schema: - type: object - properties: - payload: - type: array - items: - type: object - properties: - attribute_key: - type: string - description: filter attribute name - filter_operator: - type: string - description: filter operator name - enum: [ equal_to, not_equal_to, contains, does_not_contain ] - values: - type: array - items: + type: number +requestBody: + required: true + content: + application/json: + schema: + type: object + properties: + payload: + type: array + items: + type: object + properties: + attribute_key: type: string - description: array of the attribute values to filter - query_operator: - type: string - description: query operator name - enum: [ AND, OR ] - example: - - attribute_key: 'browser_language' - filter_operator: 'not_eq' - values: ['en'] - query_operator: 'AND' - - attribute_key: 'status' - filter_operator: 'eq' - values: ['pending'] - query_operator: null + description: filter attribute name + filter_operator: + type: string + description: filter operator name + enum: [equal_to, not_equal_to, contains, does_not_contain] + values: + type: array + items: + type: string + description: array of the attribute values to filter + query_operator: + type: string + description: query operator name + enum: [AND, OR] + example: + - attribute_key: 'browser_language' + filter_operator: 'not_equal_to' + values: ['en'] + query_operator: 'AND' + - attribute_key: 'status' + filter_operator: 'equal_to' + values: ['pending'] + query_operator: null responses: - 200: + '200': description: Success - schema: - $ref: '#/definitions/conversation_list' - 400: + content: + application/json: + schema: + $ref: '#/components/schemas/conversation_list' + '400': description: Bad Request Error - schema: - $ref: '#/definitions/bad_request_error' + content: + application/json: + schema: + $ref: '#/components/schemas/bad_request_error' diff --git a/swagger/paths/application/conversation/index.yml b/swagger/paths/application/conversation/index.yml index 6980eb2bd..e58659530 100644 --- a/swagger/paths/application/conversation/index.yml +++ b/swagger/paths/application/conversation/index.yml @@ -1,5 +1,5 @@ parameters: - - $ref: '#/parameters/account_id' + - $ref: '#/components/parameters/account_id' get: tags: @@ -7,138 +7,98 @@ get: operationId: conversationList description: List all the conversations with pagination summary: Conversations List + security: + - userApiKey: [] parameters: - name: assignee_type in: query - type: string - enum: ['me', 'unassigned', 'all', 'assigned'] - default: 'all' + schema: + type: string + enum: ['me', 'unassigned', 'all', 'assigned'] + default: 'all' description: Filter conversations by assignee type. - name: status in: query - type: string - enum: ['all', 'open', 'resolved', 'pending', 'snoozed'] - default: 'open' + schema: + type: string + enum: ['all', 'open', 'resolved', 'pending', 'snoozed'] + default: 'open' description: Filter by conversation status. - name: q in: query - type: string + schema: + type: string description: Filters conversations with messages containing the search term - name: inbox_id in: query - type: integer + schema: + type: integer - name: team_id in: query - type: integer + schema: + type: integer - name: labels in: query - type: array - items: - type: string + schema: + type: array + items: + type: string - name: page in: query - type: integer - default: 1 + schema: + type: integer + default: 1 description: paginate through conversations responses: - 200: + '200': description: Success - schema: - $ref: '#/definitions/conversation_list' - 400: + content: + application/json: + schema: + $ref: '#/components/schemas/conversation_list' + '400': description: Bad Request Error - schema: - $ref: '#/definitions/bad_request_error' + content: + application/json: + schema: + $ref: '#/components/schemas/bad_request_error' post: tags: - Conversations operationId: newConversation summary: Create New Conversation - description: "Creating a conversation in chatwoot requires a source id. \n\n Learn more about source_id: https://github.com/chatwoot/chatwoot/wiki/Building-on-Top-of-Chatwoot:-Importing-Existing-Contacts-and-Creating-Conversations" + description: "Creating a conversation in chatwoot requires a source id. \n\n Learn more about source_id: https://www.chatwoot.com/hc/user-guide/articles/1677839703-how-to-create-an-api-channel-inbox#send-messages-to-the-api-channel" security: - userApiKey: [] - agentBotApiKey: [] - parameters: - - name: data - in: body - required: true - schema: - type: object - required: - - source_id - - inbox_id - properties: - source_id: - type: string - description: Conversation source id - inbox_id: - type: string - description: "Id of inbox in which the conversation is created Allowed Inbox Types: Website, Phone, Api, Email " - contact_id: - type: string - description: Contact Id for which conversation is created - additional_attributes: - type: object - description: Lets you specify attributes like browser information - custom_attributes: - type: object - description: The object to save custom attributes for conversation, accepts custom attributes key and value - example: { attribute_key: attribute_value, priority_conversation_number: 3 } - status: - type: string - enum: ['open', 'resolved', 'pending'] - description: Specify the conversation whether it's pending, open, closed - assignee_id: - type: string - description: Agent Id for assigning a conversation to an agent - team_id: - type: string - description: Team Id for assigning a conversation to a team - message: - type: object - description: The initial message to be sent to the conversation - required: ['content'] - properties: - content: - type: string - description: The content of the message - template_params: - type: object - description: The template params for the message in case of whatsapp Channel - properties: - name: - type: string - description: Name of the template - example: 'sample_issue_resolution' - category: - type: string - description: Category of the template - example: UTILITY - language: - type: string - description: Language of the template - example: en_US - processed_params: - type: object - description: The processed param values for template variables in template - example: - 1: "Chatwoot" + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/conversation_create_payload' responses: - 200: + '200': description: Success - schema: - type: object - properties: - id: - type: number - description: ID of the conversation - account_id: - type: number - description: Account Id - inbox_id: - type: number - description: ID of the inbox - 403: + content: + application/json: + schema: + type: object + properties: + id: + type: number + description: ID of the conversation + account_id: + type: number + description: Account Id + inbox_id: + type: number + description: ID of the inbox + '403': description: Access denied + content: + application/json: + schema: + $ref: '#/components/schemas/bad_request_error' diff --git a/swagger/paths/application/conversation/labels/create.yml b/swagger/paths/application/conversation/labels/create.yml index 0f686ee24..6df685ccd 100644 --- a/swagger/paths/application/conversation/labels/create.yml +++ b/swagger/paths/application/conversation/labels/create.yml @@ -1,26 +1,41 @@ tags: - - Conversation Labels + - Conversations operationId: conversation-add-labels summary: Add Labels +security: + - userApiKey: [] description: Add labels to a conversation. Note that this API would overwrite the existing list of labels associated to the conversation. -parameters: - - name: data - in: body - required: true - schema: - type: object - properties: - labels: - type: array - description: Array of labels (comma-separated strings) - items: - type: string +requestBody: + required: true + content: + application/json: + schema: + type: object + required: + - labels + properties: + labels: + type: array + description: Array of labels (comma-separated strings) + items: + type: string + example: ['support', 'billing'] responses: - 200: + '200': description: Success - schema: - $ref: '#/definitions/conversation_labels' - 404: + content: + application/json: + schema: + $ref: '#/components/schemas/conversation_labels' + '404': description: Conversation not found - 401: + content: + application/json: + schema: + $ref: '#/components/schemas/bad_request_error' + '401': description: Unauthorized + content: + application/json: + schema: + $ref: '#/components/schemas/bad_request_error' diff --git a/swagger/paths/application/conversation/labels/index.yml b/swagger/paths/application/conversation/labels/index.yml index 107fc0132..5c8b3e597 100644 --- a/swagger/paths/application/conversation/labels/index.yml +++ b/swagger/paths/application/conversation/labels/index.yml @@ -1,14 +1,26 @@ tags: - - Conversation Labels + - Conversations operationId: list-all-labels-of-a-conversation summary: List Labels +security: + - userApiKey: [] description: Lists all the labels of a conversation responses: - 200: + '200': description: Success - schema: - $ref: '#/definitions/conversation_labels' - 404: + content: + application/json: + schema: + $ref: '#/components/schemas/conversation_labels' + '404': description: Conversation not found - 401: + content: + application/json: + schema: + $ref: '#/components/schemas/bad_request_error' + '401': description: Unauthorized + content: + application/json: + schema: + $ref: '#/components/schemas/bad_request_error' diff --git a/swagger/paths/application/conversation/messages/create.yml b/swagger/paths/application/conversation/messages/create.yml index 4ab9e27a0..f8cd35f3c 100644 --- a/swagger/paths/application/conversation/messages/create.yml +++ b/swagger/paths/application/conversation/messages/create.yml @@ -6,20 +6,30 @@ description: Create a new message in the conversation security: - userApiKey: [] - agentBotApiKey: [] -parameters: - - name: data - in: body - required: true - schema: - $ref: '#/definitions/conversation_message_create' +requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/conversation_message_create_payload' responses: - 200: + '200': description: Success - schema: - allOf: - - $ref: '#/definitions/generic_id' - - $ref: '#/definitions/message' - 404: + content: + application/json: + schema: + allOf: + - $ref: '#/components/schemas/generic_id' + - $ref: '#/components/schemas/message' + '404': description: Conversation not found - 403: + content: + application/json: + schema: + $ref: '#/components/schemas/bad_request_error' + '403': description: Access denied + content: + application/json: + schema: + $ref: '#/components/schemas/bad_request_error' diff --git a/swagger/paths/application/conversation/messages/create_attachment.yml b/swagger/paths/application/conversation/messages/create_attachment.yml index 594f9a819..63acd0aa5 100644 --- a/swagger/paths/application/conversation/messages/create_attachment.yml +++ b/swagger/paths/application/conversation/messages/create_attachment.yml @@ -3,46 +3,55 @@ post: - Messages operationId: conversationNewMessageAttachment summary: Create New Message Attachment - description: Create an attachment message. Refer to this discussion if you have any further doubts. https://github.com/chatwoot/chatwoot/discussions/1809#discussioncomment-1211845 - consumes: - - multipart/form-data + description: Create an attachment message. security: - userApiKey: [] - - agentBotApiKey: [] parameters: - - $ref: '#/parameters/account_id' - - $ref: '#/parameters/conversation_id' - required: true - - in: formData - name: content - type: string - description: The content of the message - required: true - - in: formData - name: message_type - type: string - enum: ['outgoing', 'incoming'] - - in: formData - name: private - type: boolean - description: Flag to identify if it is a private note - - in: formData - name: attachments[] - type: array - description: The files to be uploaded. - items: - type: string - format: binary - + - $ref: '#/components/parameters/account_id' + - $ref: '#/components/parameters/conversation_id' + requestBody: + required: true + content: + multipart/form-data: + schema: + type: object + required: + - content + properties: + content: + type: string + description: The content of the message + message_type: + type: string + enum: ['outgoing', 'incoming'] + private: + type: boolean + description: Flag to identify if it is a private note + attachments: + type: array + description: The files to be uploaded. + items: + type: string + format: binary responses: - 200: + '200': description: Success - schema: - allOf: - - $ref: '#/definitions/generic_id' - - $ref: '#/definitions/message' - 404: + content: + application/json: + schema: + allOf: + - $ref: '#/components/schemas/generic_id' + - $ref: '#/components/schemas/message' + '404': description: Conversation not found - 403: + content: + application/json: + schema: + $ref: '#/components/schemas/bad_request_error' + '403': description: Access denied + content: + application/json: + schema: + $ref: '#/components/schemas/bad_request_error' diff --git a/swagger/paths/application/conversation/messages/delete.yml b/swagger/paths/application/conversation/messages/delete.yml index f038074c7..73b1e495b 100644 --- a/swagger/paths/application/conversation/messages/delete.yml +++ b/swagger/paths/application/conversation/messages/delete.yml @@ -2,11 +2,21 @@ tags: - Messages operationId: delete-a-message summary: Delete a message +security: + - userApiKey: [] description: Delete a message and it's attachments from the conversation. responses: - 200: + '200': description: Success - 401: + '401': description: Unauthorized - 404: + content: + application/json: + schema: + $ref: '#/components/schemas/bad_request_error' + '404': description: The message or conversation does not exist in the account + content: + application/json: + schema: + $ref: '#/components/schemas/bad_request_error' diff --git a/swagger/paths/application/conversation/messages/index.yml b/swagger/paths/application/conversation/messages/index.yml index d99e8393b..02693a244 100644 --- a/swagger/paths/application/conversation/messages/index.yml +++ b/swagger/paths/application/conversation/messages/index.yml @@ -2,18 +2,50 @@ tags: - Messages operationId: list-all-messages summary: Get messages +security: + - userApiKey: [] description: List all messages of a conversation responses: - 200: + '200': description: Success - schema: - type: array - description: Array of messages - items: - allOf: - - $ref: '#/definitions/generic_id' - - $ref: '#/definitions/message' - 404: + content: + application/json: + schema: + type: object + properties: + meta: + type: object + properties: + labels: + type: array + items: + type: string + additional_attributes: + type: object + contact: + $ref: '#/components/schemas/contact' + assignee: + $ref: '#/components/schemas/agent' + agent_last_seen_at: + type: string + format: date-time + assignee_last_seen_at: + type: string + format: date-time + payload: + type: array + description: Array of messages + items: + $ref: '#/components/schemas/message' + '404': description: Conversation not found - 401: + content: + application/json: + schema: + $ref: '#/components/schemas/bad_request_error' + '401': description: Unauthorized + content: + application/json: + schema: + $ref: '#/components/schemas/bad_request_error' diff --git a/swagger/paths/application/conversation/meta.yml b/swagger/paths/application/conversation/meta.yml index 327fb702e..811ebbf5e 100644 --- a/swagger/paths/application/conversation/meta.yml +++ b/swagger/paths/application/conversation/meta.yml @@ -1,5 +1,5 @@ parameters: - - $ref: '#/parameters/account_id' + - $ref: '#/components/parameters/account_id' get: tags: @@ -7,47 +7,58 @@ get: operationId: conversationListMeta description: Get open, unassigned and all Conversation counts summary: Get Conversation Counts + security: + - userApiKey: [] parameters: - name: status in: query - type: string - enum: ['all', 'open', 'resolved', 'pending', 'snoozed'] - default: 'open' + schema: + type: string + enum: ['all', 'open', 'resolved', 'pending', 'snoozed'] + default: 'open' description: Filter by conversation status. - name: q in: query - type: string + schema: + type: string description: Filters conversations with messages containing the search term - name: inbox_id in: query - type: integer + schema: + type: integer - name: team_id in: query - type: integer + schema: + type: integer - name: labels in: query - type: array - items: - type: string + schema: + type: array + items: + type: string responses: - 200: + '200': description: Success - schema: - type: object - properties: - meta: + content: + application/json: + schema: type: object properties: - mine_count: - type: number - unassigned_count: - type: number - assigned_count: - type: number - all_count: - type: number - 400: + meta: + type: object + properties: + mine_count: + type: number + unassigned_count: + type: number + assigned_count: + type: number + all_count: + type: number + '400': description: Bad Request Error - schema: - $ref: '#/definitions/bad_request_error' \ No newline at end of file + content: + application/json: + schema: + $ref: '#/components/schemas/bad_request_error' diff --git a/swagger/paths/application/conversation/show.yml b/swagger/paths/application/conversation/show.yml index 56c3a292b..a57c36051 100644 --- a/swagger/paths/application/conversation/show.yml +++ b/swagger/paths/application/conversation/show.yml @@ -2,13 +2,25 @@ tags: - Conversations operationId: get-details-of-a-conversation summary: Conversation Details +security: + - userApiKey: [] description: Get all details regarding a conversation with all messages in the conversation responses: - 200: + '200': description: Success - schema: - $ref: '#/definitions/conversation_show' - 404: - description: Conversation not found - 403: + content: + application/json: + schema: + $ref: '#/components/schemas/conversation_show' + '403': description: Access denied + content: + application/json: + schema: + $ref: '#/components/schemas/bad_request_error' + '404': + description: Conversation not found + content: + application/json: + schema: + $ref: '#/components/schemas/bad_request_error' diff --git a/swagger/paths/application/conversation/toggle_priority.yml b/swagger/paths/application/conversation/toggle_priority.yml index 22a80c891..7ae3c252f 100644 --- a/swagger/paths/application/conversation/toggle_priority.yml +++ b/swagger/paths/application/conversation/toggle_priority.yml @@ -6,23 +6,32 @@ description: Toggles the priority of conversation security: - userApiKey: [] - agentBotApiKey: [] -parameters: - - name: data - in: body - required: true - schema: - type: object - required: - - priority - properties: - priority: - type: string - enum: ["urgent", "high", "medium", "low", "none"] - description: "The priority of the conversation" +requestBody: + required: true + content: + application/json: + schema: + type: object + required: + - priority + properties: + priority: + type: string + enum: ['urgent', 'high', 'medium', 'low', 'none'] + description: 'The priority of the conversation' + example: 'high' responses: - 200: + '200': description: Success - 404: - description: Conversation not found - 401: + '401': description: Unauthorized + content: + application/json: + schema: + $ref: '#/components/schemas/bad_request_error' + '404': + description: Conversation not found + content: + application/json: + schema: + $ref: '#/components/schemas/bad_request_error' diff --git a/swagger/paths/application/conversation/toggle_status.yml b/swagger/paths/application/conversation/toggle_status.yml index 20b18d6aa..9c6f9ee6a 100644 --- a/swagger/paths/application/conversation/toggle_status.yml +++ b/swagger/paths/application/conversation/toggle_status.yml @@ -6,25 +6,36 @@ description: Toggles the status of the conversation between open and resolved security: - userApiKey: [] - agentBotApiKey: [] -parameters: - - name: data - in: body - required: true - schema: - type: object - required: - - status - properties: - status: - type: string - enum: ["open", "resolved", "pending"] - description: The status of the conversation +requestBody: + required: true + content: + application/json: + schema: + type: object + required: + - status + properties: + status: + type: string + enum: ['open', 'resolved', 'pending'] + description: The status of the conversation + example: open responses: - 200: + '200': description: Success - schema: - $ref: '#/definitions/conversation_status_toggle' - 404: + content: + application/json: + schema: + $ref: '#/components/schemas/conversation_status_toggle' + '404': description: Conversation not found - 401: + content: + application/json: + schema: + $ref: '#/components/schemas/bad_request_error' + '401': description: Unauthorized + content: + application/json: + schema: + $ref: '#/components/schemas/bad_request_error' diff --git a/swagger/paths/application/conversation/update.yml b/swagger/paths/application/conversation/update.yml index 3add02635..fbe8e668b 100644 --- a/swagger/paths/application/conversation/update.yml +++ b/swagger/paths/application/conversation/update.yml @@ -6,24 +6,34 @@ description: Update Conversation Attributes security: - userApiKey: [] - agentBotApiKey: [] -parameters: - - name: data - in: body - required: true - schema: - type: object - properties: - priority: - type: string - enum: ["urgent", "high", "medium", "low", "none"] - description: "The priority of the conversation" - sla_policy_id: - type: number - description: "The ID of the SLA policy (Available only in Enterprise edition)" +requestBody: + required: true + content: + application/json: + schema: + type: object + properties: + priority: + type: string + enum: ['urgent', 'high', 'medium', 'low', 'none'] + description: 'The priority of the conversation' + example: 'high' + sla_policy_id: + type: number + description: 'The ID of the SLA policy (Available only in Enterprise edition)' + example: 1 responses: - 200: + '200': description: Success - 404: - description: Conversation not found - 401: + '401': description: Unauthorized + content: + application/json: + schema: + $ref: '#/components/schemas/bad_request_error' + '404': + description: Conversation not found + content: + application/json: + schema: + $ref: '#/components/schemas/bad_request_error' diff --git a/swagger/paths/application/conversation/update_last_seen.yml b/swagger/paths/application/conversation/update_last_seen.yml index 0f542dd7f..a81f6f219 100644 --- a/swagger/paths/application/conversation/update_last_seen.yml +++ b/swagger/paths/application/conversation/update_last_seen.yml @@ -3,6 +3,8 @@ post: - Conversations operationId: conversationUpdateLastSeen summary: Update Last Seen + security: + - userApiKey: [] description: Updates the last seen of the conversation so that conversations will have the bubbles in the agents screen parameters: - name: id @@ -11,9 +13,17 @@ post: description: ID of the conversation required: true responses: - 200: + '200': description: Success - 404: - description: Contact not found - 403: + '403': description: Access denied + content: + application/json: + schema: + $ref: '#/components/schemas/bad_request_error' + '404': + description: Contact not found + content: + application/json: + schema: + $ref: '#/components/schemas/bad_request_error' diff --git a/swagger/paths/application/custom_attributes/create.yml b/swagger/paths/application/custom_attributes/create.yml index fa01fc8e4..615dd6c74 100644 --- a/swagger/paths/application/custom_attributes/create.yml +++ b/swagger/paths/application/custom_attributes/create.yml @@ -5,16 +5,22 @@ summary: Add a new custom attribute description: Add a new custom attribute to account security: - userApiKey: [] -parameters: - - name: data - in: body - required: true - schema: - $ref: '#/definitions/custom_attribute_create_update_payload' +requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/custom_attribute_create_update_payload' responses: - 200: + '200': description: Success - schema: - $ref: '#/definitions/custom_attribute' - 403: + content: + application/json: + schema: + $ref: '#/components/schemas/custom_attribute' + '403': description: Access denied + content: + application/json: + schema: + $ref: '#/components/schemas/bad_request_error' diff --git a/swagger/paths/application/custom_attributes/delete.yml b/swagger/paths/application/custom_attributes/delete.yml index 0c9aaedee..313425a71 100644 --- a/swagger/paths/application/custom_attributes/delete.yml +++ b/swagger/paths/application/custom_attributes/delete.yml @@ -6,16 +6,25 @@ description: Remove a custom attribute from account security: - userApiKey: [] parameters: - - $ref: '#/parameters/account_id' + - $ref: '#/components/parameters/account_id' - in: path name: id - type: integer + schema: + type: integer required: true description: The ID of the custom attribute to be deleted responses: - 200: + '200': description: Success - 404: + '404': description: Custom attribute not found - 403: + content: + application/json: + schema: + $ref: '#/components/schemas/bad_request_error' + '403': description: Access denied + content: + application/json: + schema: + $ref: '#/components/schemas/bad_request_error' diff --git a/swagger/paths/application/custom_attributes/index.yml b/swagger/paths/application/custom_attributes/index.yml index a3a91144b..2d61a0925 100644 --- a/swagger/paths/application/custom_attributes/index.yml +++ b/swagger/paths/application/custom_attributes/index.yml @@ -3,22 +3,29 @@ tags: operationId: get-account-custom-attribute summary: List all custom attributes in an account parameters: - - name: attribute_model - in: query + - name: attribute_model + in: query + schema: type: string enum: ['0', '1'] - description: conversation_attribute(0)/contact_attribute(1) - required: true + description: conversation_attribute(0)/contact_attribute(1) + required: true description: Get details of custom attributes in an Account security: - userApiKey: [] responses: - 200: + '200': description: Success - schema: - type: array - description: 'Array of all custom attributes' - items: - $ref: '#/definitions/custom_attribute' - 403: + content: + application/json: + schema: + type: array + description: 'Array of all custom attributes' + items: + $ref: '#/components/schemas/custom_attribute' + '403': description: Access denied + content: + application/json: + schema: + $ref: '#/components/schemas/bad_request_error' diff --git a/swagger/paths/application/custom_attributes/show.yml b/swagger/paths/application/custom_attributes/show.yml index 8449ba9be..9910f7f85 100644 --- a/swagger/paths/application/custom_attributes/show.yml +++ b/swagger/paths/application/custom_attributes/show.yml @@ -2,20 +2,33 @@ tags: - Custom Attributes operationId: get-details-of-a-single-custom-attribute summary: Get a custom attribute details +security: + - userApiKey: [] description: Get the details of a custom attribute in the account parameters: - - $ref: '#/parameters/account_id' + - $ref: '#/components/parameters/account_id' - in: path name: id - type: integer + schema: + type: integer required: true description: The ID of the custom attribute to be updated. responses: - 200: + '200': description: Success - schema: - $ref: '#/definitions/custom_attribute' - 401: + content: + application/json: + schema: + $ref: '#/components/schemas/custom_attribute' + '401': description: Unauthorized - 404: + content: + application/json: + schema: + $ref: '#/components/schemas/bad_request_error' + '404': description: The given attribute ID does not exist in the account + content: + application/json: + schema: + $ref: '#/components/schemas/bad_request_error' diff --git a/swagger/paths/application/custom_attributes/update.yml b/swagger/paths/application/custom_attributes/update.yml index 4b69f21bd..d3ccc6af1 100644 --- a/swagger/paths/application/custom_attributes/update.yml +++ b/swagger/paths/application/custom_attributes/update.yml @@ -8,21 +8,33 @@ security: parameters: - in: path name: id - type: integer + schema: + type: integer required: true description: The ID of the custom attribute to be updated. - - name: data - in: body - required: true - schema: - $ref: '#/definitions/custom_attribute_create_update_payload' +requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/custom_attribute_create_update_payload' responses: - 200: + '200': description: Success - schema: - description: 'The updated custom attribute' - $ref: '#/definitions/custom_attribute' - 404: + content: + application/json: + schema: + description: 'The updated custom attribute' + $ref: '#/components/schemas/custom_attribute' + '404': description: Agent not found - 403: + content: + application/json: + schema: + $ref: '#/components/schemas/bad_request_error' + '403': description: Access denied + content: + application/json: + schema: + $ref: '#/components/schemas/bad_request_error' diff --git a/swagger/paths/application/custom_filters/create.yml b/swagger/paths/application/custom_filters/create.yml index 9d9534976..5b7a6ca80 100644 --- a/swagger/paths/application/custom_filters/create.yml +++ b/swagger/paths/application/custom_filters/create.yml @@ -4,16 +4,25 @@ operationId: create-a-custom-filter summary: Create a custom filter description: Create a custom filter in the account parameters: - - $ref: '#/parameters/account_id' - - name: data - in: body - required: true - schema: - $ref: '#/definitions/custom_filter_create_update_payload' + - $ref: '#/components/parameters/account_id' +security: + - userApiKey: [] +requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/custom_filter_create_update_payload' responses: - 200: + '200': description: Success - schema: - $ref: '#/definitions/custom_filter' - 401: + content: + application/json: + schema: + $ref: '#/components/schemas/custom_filter' + '401': description: Unauthorized + content: + application/json: + schema: + $ref: '#/components/schemas/bad_request_error' diff --git a/swagger/paths/application/custom_filters/delete.yml b/swagger/paths/application/custom_filters/delete.yml index e66748858..5553bbeae 100644 --- a/swagger/paths/application/custom_filters/delete.yml +++ b/swagger/paths/application/custom_filters/delete.yml @@ -2,11 +2,21 @@ tags: - Custom Filters operationId: delete-a-custom-filter summary: Delete a custom filter +security: + - userApiKey: [] description: Delete a custom filter from the account responses: - 200: + '200': description: Success - 401: + '401': description: Unauthorized - 404: + content: + application/json: + schema: + $ref: '#/components/schemas/bad_request_error' + '404': description: The custom filter does not exist in the account + content: + application/json: + schema: + $ref: '#/components/schemas/bad_request_error' diff --git a/swagger/paths/application/custom_filters/index.yml b/swagger/paths/application/custom_filters/index.yml index b77949dc3..0c4f7578f 100644 --- a/swagger/paths/application/custom_filters/index.yml +++ b/swagger/paths/application/custom_filters/index.yml @@ -3,13 +3,21 @@ tags: operationId: list-all-filters summary: List all custom filters description: List all custom filters in a category of a user +security: + - userApiKey: [] responses: - 200: + '200': description: Success - schema: - type: array - description: 'Array of custom filters' - items: - $ref: '#/definitions/custom_filter' - 401: + content: + application/json: + schema: + type: array + description: 'Array of custom filters' + items: + $ref: '#/components/schemas/custom_filter' + '401': description: Unauthorized + content: + application/json: + schema: + $ref: '#/components/schemas/bad_request_error' diff --git a/swagger/paths/application/custom_filters/show.yml b/swagger/paths/application/custom_filters/show.yml index 14de07d85..cf298a4ad 100644 --- a/swagger/paths/application/custom_filters/show.yml +++ b/swagger/paths/application/custom_filters/show.yml @@ -3,12 +3,24 @@ tags: operationId: get-details-of-a-single-custom-filter summary: Get a custom filter details description: Get the details of a custom filter in the account +security: + - userApiKey: [] responses: - 200: + '200': description: Success - schema: - $ref: '#/definitions/custom_filter' - 401: + content: + application/json: + schema: + $ref: '#/components/schemas/custom_filter' + '401': description: Unauthorized - 404: + content: + application/json: + schema: + $ref: '#/components/schemas/bad_request_error' + '404': description: The given team ID does not exist in the account + content: + application/json: + schema: + $ref: '#/components/schemas/bad_request_error' diff --git a/swagger/paths/application/custom_filters/update.yml b/swagger/paths/application/custom_filters/update.yml index a058b81f8..19db632d2 100644 --- a/swagger/paths/application/custom_filters/update.yml +++ b/swagger/paths/application/custom_filters/update.yml @@ -2,17 +2,25 @@ tags: - Custom Filters operationId: update-a-custom-filter summary: Update a custom filter +security: + - userApiKey: [] description: Update a custom filter's attributes -parameters: - - name: data - in: body - required: true - schema: - $ref: '#/definitions/custom_filter_create_update_payload' +requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/custom_filter_create_update_payload' responses: - 200: + '200': description: Success - schema: - $ref: '#/definitions/custom_filter' - 401: + content: + application/json: + schema: + $ref: '#/components/schemas/custom_filter' + '401': description: Unauthorized + content: + application/json: + schema: + $ref: '#/components/schemas/bad_request_error' diff --git a/swagger/paths/application/inboxes/create.yml b/swagger/paths/application/inboxes/create.yml index 3d649e820..6f88b0d4a 100644 --- a/swagger/paths/application/inboxes/create.yml +++ b/swagger/paths/application/inboxes/create.yml @@ -4,48 +4,32 @@ post: operationId: inboxCreation summary: Create an inbox description: You can create more than one website inbox in each account + security: + - userApiKey: [] parameters: - - $ref: '#/parameters/account_id' - - name: data - in: body - required: true - schema: - type: object - properties: - name: - type: string - description: The name of the inbox - avatar: - type: string - format: binary - description: File for avatar image - channel: - type: object - properties: - type: - type: string - enum: ['web_widget'] - website_url: - type: string - description: URL at which the widget will be loaded - welcome_title: - type: string - description: Welcome title to be displayed on the widget - welcome_tagline: - type: string - description: Welcome tagline to be displayed on the widget - agent_away_message: - type: string - description: A message which will be sent if there is not agent available. This is not available if agentbot is connected - widget_color: - type: string - description: A Hex-color string used to customize the widget + - $ref: '#/components/parameters/account_id' + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/inbox_create_payload' responses: - 200: + '200': description: Success - schema: - $ref: '#/definitions/inbox' - 404: + content: + application/json: + schema: + $ref: '#/components/schemas/inbox' + '404': description: Inbox not found - 403: + content: + application/json: + schema: + $ref: '#/components/schemas/bad_request_error' + '403': description: Access denied + content: + application/json: + schema: + $ref: '#/components/schemas/bad_request_error' diff --git a/swagger/paths/application/inboxes/get_agent_bot.yml b/swagger/paths/application/inboxes/get_agent_bot.yml index 178151dd3..f10d6b41f 100644 --- a/swagger/paths/application/inboxes/get_agent_bot.yml +++ b/swagger/paths/application/inboxes/get_agent_bot.yml @@ -4,19 +4,32 @@ get: operationId: getInboxAgentBot summary: Show Inbox Agent Bot description: See if an agent bot is associated to the Inbox + security: + - userApiKey: [] parameters: - - $ref: '#/parameters/account_id' + - $ref: '#/components/parameters/account_id' - name: id in: path - type: number + schema: + type: number description: ID of the inbox required: true responses: - 204: + '204': description: Success - schema: - $ref: '#/definitions/agent_bot' - 404: + content: + application/json: + schema: + $ref: '#/components/schemas/agent_bot' + '404': description: Inbox not found, Agent bot not found - 403: + content: + application/json: + schema: + $ref: '#/components/schemas/bad_request_error' + '403': description: Access denied + content: + application/json: + schema: + $ref: '#/components/schemas/bad_request_error' diff --git a/swagger/paths/application/inboxes/inbox_members/create.yml b/swagger/paths/application/inboxes/inbox_members/create.yml index 9ca5641e6..47dcb8b2e 100644 --- a/swagger/paths/application/inboxes/inbox_members/create.yml +++ b/swagger/paths/application/inboxes/inbox_members/create.yml @@ -5,35 +5,54 @@ summary: Add a New Agent description: Add a new Agent to Inbox security: - userApiKey: [] -parameters: - - name: data - in: body - required: true - schema: - type: object - required: +requestBody: + required: true + content: + application/json: + schema: + type: object + required: - inbox_id - user_ids - properties: - inbox_id: - type: string - description: The ID of the inbox - user_ids: - type: array - items: + properties: + inbox_id: type: integer - description: IDs of users to be added to the inbox + description: The ID of the inbox + example: 1 + user_ids: + type: array + items: + type: integer + description: IDs of users to be added to the inbox + example: [1] responses: - 200: + '200': description: Success - schema: - type: array - description: 'Array of all active agents' - items: - $ref: '#/definitions/agent' - 404: + content: + application/json: + schema: + type: object + properties: + payload: + type: array + description: 'Array of all active agents' + items: + $ref: '#/components/schemas/agent' + '404': description: Inbox not found - 403: + content: + application/json: + schema: + $ref: '#/components/schemas/bad_request_error' + '403': description: Access denied - 422: + content: + application/json: + schema: + $ref: '#/components/schemas/bad_request_error' + '422': description: User must exist + content: + application/json: + schema: + $ref: '#/components/schemas/bad_request_error' diff --git a/swagger/paths/application/inboxes/inbox_members/delete.yml b/swagger/paths/application/inboxes/inbox_members/delete.yml index 78f1435b5..a8df7661a 100644 --- a/swagger/paths/application/inboxes/inbox_members/delete.yml +++ b/swagger/paths/application/inboxes/inbox_members/delete.yml @@ -5,30 +5,42 @@ summary: Remove an Agent from Inbox description: Remove an Agent from Inbox security: - userApiKey: [] -parameters: - - name: data - in: body - required: true - schema: - type: object - required: - - inbox_id - - user_ids - properties: - inbox_id: - type: string - description: The ID of the inbox - user_ids: - type: array - items: - type: integer - description: IDs of users to be deleted from the inbox +requestBody: + required: true + content: + application/json: + schema: + type: object + required: + - inbox_id + - user_ids + properties: + inbox_id: + type: string + description: The ID of the inbox + user_ids: + type: array + items: + type: integer + description: IDs of users to be deleted from the inbox responses: - 200: + '200': description: Success - 404: + '404': description: Inbox not found - 403: + content: + application/json: + schema: + $ref: '#/components/schemas/bad_request_error' + '403': description: Access denied - 422: + content: + application/json: + schema: + $ref: '#/components/schemas/bad_request_error' + '422': description: User must exist + content: + application/json: + schema: + $ref: '#/components/schemas/bad_request_error' diff --git a/swagger/paths/application/inboxes/inbox_members/show.yml b/swagger/paths/application/inboxes/inbox_members/show.yml index 57793f520..ec10a5f0b 100644 --- a/swagger/paths/application/inboxes/inbox_members/show.yml +++ b/swagger/paths/application/inboxes/inbox_members/show.yml @@ -6,16 +6,29 @@ description: Get Details of Agents in an Inbox security: - userApiKey: [] parameters: - - $ref: '#/parameters/inbox_id' + - $ref: '#/components/parameters/inbox_id' responses: - 200: + '200': description: Success - schema: - type: array - description: 'Array of all active agents' - items: - $ref: '#/definitions/agent' - 404: + content: + application/json: + schema: + type: object + properties: + payload: + type: array + description: 'Array of all active agents' + items: + $ref: '#/components/schemas/agent' + '404': description: Inbox not found - 403: + content: + application/json: + schema: + $ref: '#/components/schemas/bad_request_error' + '403': description: Access denied + content: + application/json: + schema: + $ref: '#/components/schemas/bad_request_error' diff --git a/swagger/paths/application/inboxes/inbox_members/update.yml b/swagger/paths/application/inboxes/inbox_members/update.yml index 483b8f6ab..728c2a028 100644 --- a/swagger/paths/application/inboxes/inbox_members/update.yml +++ b/swagger/paths/application/inboxes/inbox_members/update.yml @@ -5,35 +5,54 @@ summary: Update Agents in Inbox description: All agents except the one passed in params will be removed security: - userApiKey: [] -parameters: - - name: data - in: body - required: true - schema: - type: object - required: - - inbox_id - - user_ids - properties: - inbox_id: - type: string - description: The ID of the inbox - user_ids: - type: array - items: - type: integer - description: IDs of users to be added to the inbox +requestBody: + required: true + content: + application/json: + schema: + type: object + required: + - inbox_id + - user_ids + properties: + inbox_id: + type: string + description: The ID of the inbox + example: 1 + user_ids: + type: array + items: + type: integer + description: IDs of users to be added to the inbox + example: [1] responses: - 200: + '200': description: Success - schema: - type: array - description: 'Array of all active agents' - items: - $ref: '#/definitions/agent' - 404: + content: + application/json: + schema: + type: object + properties: + payload: + type: array + description: 'Array of all active agents' + items: + $ref: '#/components/schemas/agent' + '404': description: Inbox not found - 403: + content: + application/json: + schema: + $ref: '#/components/schemas/bad_request_error' + '403': description: Access denied - 422: + content: + application/json: + schema: + $ref: '#/components/schemas/bad_request_error' + '422': description: User must exist + content: + application/json: + schema: + $ref: '#/components/schemas/bad_request_error' diff --git a/swagger/paths/application/inboxes/index.yml b/swagger/paths/application/inboxes/index.yml index 233488228..89abb6009 100644 --- a/swagger/paths/application/inboxes/index.yml +++ b/swagger/paths/application/inboxes/index.yml @@ -4,17 +4,32 @@ get: operationId: listAllInboxes summary: List all inboxes description: List all inboxes available in the current account + security: + - userApiKey: [] parameters: - - $ref: '#/parameters/account_id' + - $ref: '#/components/parameters/account_id' responses: - 200: + '200': description: Success - schema: - type: array - description: 'Array of inboxes' - items: - $ref: '#/definitions/inbox' - 404: + content: + application/json: + schema: + type: object + properties: + payload: + type: array + description: 'Array of inboxes' + items: + $ref: '#/components/schemas/inbox' + '404': description: Inbox not found - 403: + content: + application/json: + schema: + $ref: '#/components/schemas/bad_request_error' + '403': description: Access denied + content: + application/json: + schema: + $ref: '#/components/schemas/bad_request_error' diff --git a/swagger/paths/application/inboxes/set_agent_bot.yml b/swagger/paths/application/inboxes/set_agent_bot.yml index 56a37d165..bbd6a9e89 100644 --- a/swagger/paths/application/inboxes/set_agent_bot.yml +++ b/swagger/paths/application/inboxes/set_agent_bot.yml @@ -3,29 +3,42 @@ post: - Inboxes operationId: updateAgentBot summary: Add or remove agent bot + security: + - userApiKey: [] description: To add an agent bot pass agent_bot id, to remove agent bot from an inbox pass null parameters: - - $ref: '#/parameters/account_id' + - $ref: '#/components/parameters/account_id' - name: id in: path - type: number + schema: + type: number description: ID of the inbox required: true - - name: data - in: body - required: true - schema: - type: object - required: - - agent_bot - properties: - agent_bot: - type: number - description: 'Agent bot ID' + requestBody: + required: true + content: + application/json: + schema: + type: object + required: + - agent_bot + properties: + agent_bot: + type: number + description: 'Agent bot ID' + example: 1 responses: - 204: + '204': description: Success - 404: + '404': description: Inbox not found, Agent bot not found - 403: + content: + application/json: + schema: + $ref: '#/components/schemas/bad_request_error' + '403': description: Access denied + content: + application/json: + schema: + $ref: '#/components/schemas/bad_request_error' diff --git a/swagger/paths/application/inboxes/show.yml b/swagger/paths/application/inboxes/show.yml index 6bb2e84a5..5d145c56e 100644 --- a/swagger/paths/application/inboxes/show.yml +++ b/swagger/paths/application/inboxes/show.yml @@ -3,20 +3,33 @@ get: - Inboxes operationId: GetInbox summary: Get an inbox + security: + - userApiKey: [] description: Get an inbox available in the current account parameters: - - $ref: '#/parameters/account_id' + - $ref: '#/components/parameters/account_id' - name: id in: path - type: number + schema: + type: number description: ID of the inbox required: true responses: - 200: + '200': description: Success - schema: - $ref: '#/definitions/inbox' - 404: + content: + application/json: + schema: + $ref: '#/components/schemas/inbox' + '404': description: Inbox not found - 403: + content: + application/json: + schema: + $ref: '#/components/schemas/bad_request_error' + '403': description: Access denied + content: + application/json: + schema: + $ref: '#/components/schemas/bad_request_error' diff --git a/swagger/paths/application/inboxes/update.yml b/swagger/paths/application/inboxes/update.yml index c5b3aaebf..e076dd532 100644 --- a/swagger/paths/application/inboxes/update.yml +++ b/swagger/paths/application/inboxes/update.yml @@ -3,58 +3,41 @@ patch: - Inboxes operationId: updateInbox summary: Update Inbox - description: Add avatar and disable auto assignment for an inbox + security: + - userApiKey: [] + description: Update an existing inbox parameters: - - $ref: '#/parameters/account_id' + - $ref: '#/components/parameters/account_id' - name: id in: path - type: number + schema: + type: number description: ID of the inbox required: true - - name: data - in: body - required: true - schema: - type: object - required: - - enable_auto_assignment - properties: - name: - type: string - description: The name of the inbox - enable_auto_assignment: - type: boolean - description: 'Enable Auto Assignment' - avatar: - type: string - format: binary - description: 'Image file for avatar' - channel: - type: object - properties: - website_url: - type: string - description: URL at which the widget will be loaded - welcome_title: - type: string - description: Welcome title to be displayed on the widget - welcome_tagline: - type: string - description: Welcome tagline to be displayed on the widget - agent_away_message: - type: string - description: A message which will be sent if there is not agent available. This is not available if agentbot is connected - widget_color: - type: string - description: A Hex-color string used to customize the widget + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/inbox_update_payload' responses: - 200: + '200': description: Success - schema: - type: object - description: 'Updated inbox object' - $ref: '#/definitions/inbox' - 404: + content: + application/json: + schema: + type: object + description: 'Updated inbox object' + $ref: '#/components/schemas/inbox' + '404': description: Inbox not found - 403: + content: + application/json: + schema: + $ref: '#/components/schemas/bad_request_error' + '403': description: Access denied + content: + application/json: + schema: + $ref: '#/components/schemas/bad_request_error' diff --git a/swagger/paths/application/integrations/apps/show.yml b/swagger/paths/application/integrations/apps/show.yml index 16ce299fe..ab5403bd6 100644 --- a/swagger/paths/application/integrations/apps/show.yml +++ b/swagger/paths/application/integrations/apps/show.yml @@ -2,16 +2,31 @@ tags: - Integrations operationId: get-details-of-all-integrations summary: List all the Integrations +security: + - userApiKey: [] description: Get the details of all Integrations available for the account responses: - 200: + '200': description: Success - schema: - type: array - description: 'Array of Integration apps' - items: - $ref: '#/definitions/integrations_app' - 401: + content: + application/json: + schema: + type: object + properties: + payload: + type: array + description: 'Array of Integration apps' + items: + $ref: '#/components/schemas/integrations_app' + '401': description: Unauthorized - 404: + content: + application/json: + schema: + $ref: '#/components/schemas/bad_request_error' + '404': description: Url not found + content: + application/json: + schema: + $ref: '#/components/schemas/bad_request_error' diff --git a/swagger/paths/application/integrations/hooks/create.yml b/swagger/paths/application/integrations/hooks/create.yml index 42bcb002e..11d43b140 100644 --- a/swagger/paths/application/integrations/hooks/create.yml +++ b/swagger/paths/application/integrations/hooks/create.yml @@ -3,17 +3,26 @@ tags: operationId: create-an-integration-hook summary: Create an integration hook description: Create an integration hook +security: + - userApiKey: [] parameters: - - $ref: '#/parameters/account_id' - - name: data - in: body - required: true - schema: - $ref: '#/definitions/integrations_hook_create_payload' + - $ref: '#/components/parameters/account_id' +requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/integrations_hook_create_payload' responses: - 200: + '200': description: Success - schema: - $ref: '#/definitions/integrations_hook' - 401: + content: + application/json: + schema: + $ref: '#/components/schemas/integrations_hook' + '401': description: Unauthorized + content: + application/json: + schema: + $ref: '#/components/schemas/bad_request_error' diff --git a/swagger/paths/application/integrations/hooks/delete.yml b/swagger/paths/application/integrations/hooks/delete.yml index 3e616613b..60172b5d8 100644 --- a/swagger/paths/application/integrations/hooks/delete.yml +++ b/swagger/paths/application/integrations/hooks/delete.yml @@ -1,15 +1,25 @@ tags: - - Integrations + - Integrations operationId: delete-an-integration-hook summary: Delete an Integration Hook description: Delete an Integration Hook +security: + - userApiKey: [] parameters: - - $ref: '#/parameters/account_id' - - $ref: '#/parameters/hook_id' + - $ref: '#/components/parameters/account_id' + - $ref: '#/components/parameters/hook_id' responses: - 200: + '200': description: Success - 401: + '401': description: Unauthorized - 404: + content: + application/json: + schema: + $ref: '#/components/schemas/bad_request_error' + '404': description: The hook does not exist in the account + content: + application/json: + schema: + $ref: '#/components/schemas/bad_request_error' diff --git a/swagger/paths/application/integrations/hooks/update.yml b/swagger/paths/application/integrations/hooks/update.yml index 2cfd6877a..eaa80cf20 100644 --- a/swagger/paths/application/integrations/hooks/update.yml +++ b/swagger/paths/application/integrations/hooks/update.yml @@ -3,18 +3,27 @@ tags: operationId: update-an-integrations-hook summary: Update an Integration Hook description: Update an Integration Hook +security: + - userApiKey: [] parameters: - - $ref: '#/parameters/account_id' - - $ref: '#/parameters/hook_id' - - name: data - in: body - required: true - schema: - $ref: '#/definitions/integrations_hook_update_payload' + - $ref: '#/components/parameters/account_id' + - $ref: '#/components/parameters/hook_id' +requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/integrations_hook_update_payload' responses: - 200: + '200': description: Success - schema: - $ref: '#/definitions/integrations_hook' - 401: + content: + application/json: + schema: + $ref: '#/components/schemas/integrations_hook' + '401': description: Unauthorized + content: + application/json: + schema: + $ref: '#/components/schemas/bad_request_error' diff --git a/swagger/paths/application/portal/create.yml b/swagger/paths/application/portal/create.yml index 187c20008..b08fc2931 100644 --- a/swagger/paths/application/portal/create.yml +++ b/swagger/paths/application/portal/create.yml @@ -5,16 +5,22 @@ summary: Add a new portal description: Add a new portal to account security: - userApiKey: [] -parameters: - - name: data - in: body - required: true - schema: - $ref: '#/definitions/portal_create_update_payload' +requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/portal_create_update_payload' responses: '200': description: Success - schema: - $ref: '#/definitions/portal' + content: + application/json: + schema: + $ref: '#/components/schemas/portal' '403': description: Access denied + content: + application/json: + schema: + $ref: '#/components/schemas/bad_request_error' diff --git a/swagger/paths/application/portal/index.yml b/swagger/paths/application/portal/index.yml index ea56563a4..1c57fc7fb 100644 --- a/swagger/paths/application/portal/index.yml +++ b/swagger/paths/application/portal/index.yml @@ -3,17 +3,58 @@ tags: operationId: get-portal summary: List all portals in an account parameters: - - $ref: '#/parameters/account_id' + - $ref: '#/components/parameters/account_id' description: Get details of portals in an Account security: - userApiKey: [] responses: '200': description: Success - schema: - type: array - description: Array of all portals - items: - $ref: '#/definitions/portal' + content: + application/json: + schema: + $ref: '#/components/schemas/portal' + example: + payload: + - id: 4 + color: "#1F93FF" + custom_domain: "chatwoot.help" + header_text: "Handbook" + homepage_link: "https://www.chatwoot.com" + name: "Handbook" + page_title: "Handbook" + slug: "handbook" + archived: false + account_id: 1 + config: + allowed_locales: + - code: "en" + articles_count: 32 + categories_count: 9 + inbox: + id: 37 + avatar_url: "https://example.com/avatar.png" + channel_id: 1 + name: "Chatwoot" + channel_type: "Channel::WebWidget" + greeting_enabled: true + widget_color: "#1F93FF" + website_url: "chatwoot.com" + logo: + id: 19399916 + portal_id: 4 + file_type: "image/png" + account_id: 1 + file_url: "https://example.com/logo.png" + blob_id: 21239614 + filename: "square.png" + meta: + all_articles_count: 0 + categories_count: 9 + default_locale: "en" '403': description: Access denied + content: + application/json: + schema: + $ref: '#/components/schemas/bad_request_error' diff --git a/swagger/paths/application/portal/show.yml b/swagger/paths/application/portal/show.yml new file mode 100644 index 000000000..20eb7e1f1 --- /dev/null +++ b/swagger/paths/application/portal/show.yml @@ -0,0 +1,79 @@ +tags: + - Help Center +operationId: get-details-of-a-single-portal +summary: Get a portal details +description: Get the details of a portal in the account +security: + - userApiKey: [] +parameters: + - $ref: '#/components/parameters/account_id' + - $ref: '#/components/parameters/portal_id' +responses: + '200': + description: Success + content: + application/json: + schema: + $ref: '#/components/schemas/portal_single' + example: + payload: + id: 123 + archived: false + color: "#1F93FF" + config: + allowed_locales: + - code: "en" + articles_count: 32 + categories_count: 9 + custom_domain: "chatwoot.help" + header_text: "Handbook" + homepage_link: "https://www.chatwoot.com" + name: "Handbook" + slug: "handbook" + page_title: "Handbook" + account_id: 123 + inbox: + id: 123 + name: "Chatwoot" + website_url: "chatwoot.com" + channel_type: "Channel::WebWidget" + avatar_url: "https://example.com/avatar.png" + widget_color: "#1F93FF" + website_token: "4cWzuf9i9jxN9tbnv8K9STKU" + enable_auto_assignment: true + web_widget_script: "" + welcome_title: "Hi there ! 🙌🏼" + welcome_tagline: "We make it simple to connect with us." + greeting_enabled: true + greeting_message: "Hey there 👋, Thank you for reaching out to us." + channel_id: 123 + working_hours_enabled: true + enable_email_collect: true + csat_survey_enabled: true + timezone: "America/Los_Angeles" + business_name: "Chatwoot" + hmac_mandatory: true + logo: + id: 123 + portal_id: 123 + file_type: "image/png" + account_id: 123 + file_url: "https://example.com/logo.png" + blob_id: 123 + filename: "square.png" + meta: + all_articles_count: 32 + categories_count: 9 + default_locale: "en" + '401': + description: Unauthorized + content: + application/json: + schema: + $ref: '#/components/schemas/bad_request_error' + '404': + description: The given portal ID does not exist in the account + content: + application/json: + schema: + $ref: '#/components/schemas/bad_request_error' \ No newline at end of file diff --git a/swagger/paths/application/portal/update.yml b/swagger/paths/application/portal/update.yml index 8c6a74e85..0679a37d0 100644 --- a/swagger/paths/application/portal/update.yml +++ b/swagger/paths/application/portal/update.yml @@ -1,20 +1,85 @@ tags: - Help Center -operationId: update-new-portal-to-account -summary: update a new portal -description: update a new portal to account +operationId: update-portal-to-account +summary: Update a portal +description: Update a portal to account security: - userApiKey: [] parameters: - - name: data - in: body - required: true - schema: - $ref: '#/definitions/portal_create_update_payload' + - $ref: '#/components/parameters/account_id' + - $ref: '#/components/parameters/portal_id' +requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/portal_create_update_payload' responses: '200': description: Success - schema: - $ref: '#/definitions/portal' + content: + application/json: + schema: + $ref: '#/components/schemas/portal_single' + example: + payload: + id: 123 + archived: false + color: "#1F93FF" + config: + allowed_locales: + - code: "en" + articles_count: 32 + categories_count: 9 + custom_domain: "chatwoot.help" + header_text: "Handbook" + homepage_link: "https://www.chatwoot.com" + name: "Handbook" + slug: "handbook" + page_title: "Handbook" + account_id: 123 + inbox: + id: 123 + name: "Chatwoot" + website_url: "chatwoot.com" + channel_type: "Channel::WebWidget" + avatar_url: "https://example.com/avatar.png" + widget_color: "#1F93FF" + website_token: "4cWzuf9i9jxN9tbnv8K9STKU" + enable_auto_assignment: true + web_widget_script: "" + welcome_title: "Hi there ! 🙌🏼" + welcome_tagline: "We make it simple to connect with us." + greeting_enabled: true + greeting_message: "Hey there 👋, Thank you for reaching out to us." + channel_id: 123 + working_hours_enabled: true + enable_email_collect: true + csat_survey_enabled: true + timezone: "America/Los_Angeles" + business_name: "Chatwoot" + hmac_mandatory: true + logo: + id: 123 + portal_id: 123 + file_type: "image/png" + account_id: 123 + file_url: "https://example.com/logo.png" + blob_id: 123 + filename: "square.png" + meta: + all_articles_count: 32 + categories_count: 9 + default_locale: "en" '403': description: Access denied + content: + application/json: + schema: + $ref: '#/components/schemas/bad_request_error' + '404': + description: Portal not found + content: + application/json: + schema: + $ref: '#/components/schemas/bad_request_error' diff --git a/swagger/paths/application/reports/conversation/account.yml b/swagger/paths/application/reports/conversation/account.yml index 28aac72e7..d0fa9a3c3 100644 --- a/swagger/paths/application/reports/conversation/account.yml +++ b/swagger/paths/application/reports/conversation/account.yml @@ -2,22 +2,33 @@ tags: - Reports operationId: get-account-conversation-metrics summary: Account Conversation Metrics +security: + - userApiKey: [] description: Get conversation metrics for Account responses: - 200: + '200': description: Success - schema: - type: object - description: 'Object of account conversation metrics' - properties: - open: - type: number - unattended: - type: number - unassigned: - type: number - - 404: + content: + application/json: + schema: + type: object + description: 'Object of account conversation metrics' + properties: + open: + type: number + unattended: + type: number + unassigned: + type: number + '404': description: reports not found - 403: + content: + application/json: + schema: + $ref: '#/components/schemas/bad_request_error' + '403': description: Access denied + content: + application/json: + schema: + $ref: '#/components/schemas/bad_request_error' diff --git a/swagger/paths/application/reports/conversation/agent.yml b/swagger/paths/application/reports/conversation/agent.yml index 90f433e4a..3cbf48403 100644 --- a/swagger/paths/application/reports/conversation/agent.yml +++ b/swagger/paths/application/reports/conversation/agent.yml @@ -2,17 +2,28 @@ tags: - Reports operationId: get-agent-conversation-metrics summary: Agent Conversation Metrics +security: + - userApiKey: [] description: Get conversation metrics for Agent responses: - 200: + '200': description: Success - schema: - type: array - description: 'Array of agent based conversation metrics' - items: - $ref: '#/definitions/agent_conversation_metrics' - - 404: + content: + application/json: + schema: + type: array + description: 'Array of agent based conversation metrics' + items: + $ref: '#/components/schemas/agent_conversation_metrics' + '404': description: reports not found - 403: + content: + application/json: + schema: + $ref: '#/components/schemas/bad_request_error' + '403': description: Access denied + content: + application/json: + schema: + $ref: '#/components/schemas/bad_request_error' diff --git a/swagger/paths/application/reports/index.yml b/swagger/paths/application/reports/index.yml index 3d86df38d..73aca86ea 100644 --- a/swagger/paths/application/reports/index.yml +++ b/swagger/paths/application/reports/index.yml @@ -2,21 +2,33 @@ tags: - Reports operationId: list-all-conversation-statistics summary: Get Account reports +security: + - userApiKey: [] description: Get Account reports for a specific type, metric and date range responses: - 200: + '200': description: Success - schema: - type: array - description: 'Array of date based conversation statistics' - items: - type: object - properties: - value: - type: string - timestamp: - type: number - 404: + content: + application/json: + schema: + type: array + description: 'Array of date based conversation statistics' + items: + type: object + properties: + value: + type: string + timestamp: + type: number + '404': description: reports not found - 403: + content: + application/json: + schema: + $ref: '#/components/schemas/bad_request_error' + '403': description: Access denied + content: + application/json: + schema: + $ref: '#/components/schemas/bad_request_error' diff --git a/swagger/paths/application/reports/summary.yml b/swagger/paths/application/reports/summary.yml index f9538a9f8..20497cc52 100644 --- a/swagger/paths/application/reports/summary.yml +++ b/swagger/paths/application/reports/summary.yml @@ -2,14 +2,26 @@ tags: - Reports operationId: list-all-conversation-statistics-summary summary: Get Account reports summary +security: + - userApiKey: [] description: Get Account reports summary for a specific type and date range responses: - 200: + '200': description: Success - schema: - description: 'Object of summary metrics' - $ref: '#/definitions/account_summary' - 404: + content: + application/json: + schema: + description: 'Object of summary metrics' + $ref: '#/components/schemas/account_summary' + '404': description: reports not found - 403: + content: + application/json: + schema: + $ref: '#/components/schemas/bad_request_error' + '403': description: Access denied + content: + application/json: + schema: + $ref: '#/components/schemas/bad_request_error' diff --git a/swagger/paths/application/team_members/create.yml b/swagger/paths/application/team_members/create.yml index 724769319..bea148f73 100644 --- a/swagger/paths/application/team_members/create.yml +++ b/swagger/paths/application/team_members/create.yml @@ -5,31 +5,46 @@ summary: Add a New Agent description: Add a new Agent to Team security: - userApiKey: [] -parameters: - - name: data - in: body - required: true - schema: - type: object - required: +requestBody: + required: true + content: + application/json: + schema: + type: object + required: - user_ids - properties: - user_ids: - type: array - items: - type: integer - description: IDs of users to be added to the team + properties: + user_ids: + type: array + items: + type: integer + description: IDs of users to be added to the team + example: [1] responses: - 200: + '200': description: Success - schema: - type: array - description: 'Array of all active agents' - items: - $ref: '#/definitions/agent' - 404: - description: Team not found - 403: + content: + application/json: + schema: + type: array + description: 'Array of all active agents' + items: + $ref: '#/components/schemas/agent' + '403': description: Access denied - 422: + content: + application/json: + schema: + $ref: '#/components/schemas/bad_request_error' + '404': + description: Team not found + content: + application/json: + schema: + $ref: '#/components/schemas/bad_request_error' + '422': description: User must exist + content: + application/json: + schema: + $ref: '#/components/schemas/bad_request_error' diff --git a/swagger/paths/application/team_members/delete.yml b/swagger/paths/application/team_members/delete.yml index ac7dbb451..99deed83d 100644 --- a/swagger/paths/application/team_members/delete.yml +++ b/swagger/paths/application/team_members/delete.yml @@ -5,27 +5,38 @@ summary: Remove an Agent from Team description: Remove an Agent from Team security: - userApiKey: [] -parameters: - - name: data - in: body - required: true - schema: - type: object - required: - - team_id - - user_ids - properties: - user_ids: - type: array - items: - type: integer - description: IDs of users to be deleted from the team +requestBody: + required: true + content: + application/json: + schema: + type: object + required: + - user_ids + properties: + user_ids: + type: array + items: + type: integer + description: IDs of users to be deleted from the team responses: - 200: + '200': description: Success - 404: - description: Team not found - 403: + '403': description: Access denied - 422: + content: + application/json: + schema: + $ref: '#/components/schemas/bad_request_error' + '404': + description: Team not found + content: + application/json: + schema: + $ref: '#/components/schemas/bad_request_error' + '422': description: User must exist + content: + application/json: + schema: + $ref: '#/components/schemas/bad_request_error' diff --git a/swagger/paths/application/team_members/index.yml b/swagger/paths/application/team_members/index.yml index c57705223..dfa505363 100644 --- a/swagger/paths/application/team_members/index.yml +++ b/swagger/paths/application/team_members/index.yml @@ -6,17 +6,27 @@ description: Get Details of Agents in an Team security: - userApiKey: [] parameters: - - $ref: '#/parameters/account_id' - - $ref: '#/parameters/team_id' + - $ref: '#/components/parameters/account_id' + - $ref: '#/components/parameters/team_id' responses: - 200: + '200': description: Success - schema: - type: array - description: 'Array of all agents in the team' - items: - $ref: '#/definitions/agent' - 404: - description: Inbox not found - 403: + content: + application/json: + schema: + type: array + description: 'Array of all agents in the team' + items: + $ref: '#/components/schemas/agent' + '403': description: Access denied + content: + application/json: + schema: + $ref: '#/components/schemas/bad_request_error' + '404': + description: Team not found + content: + application/json: + schema: + $ref: '#/components/schemas/bad_request_error' diff --git a/swagger/paths/application/team_members/update.yml b/swagger/paths/application/team_members/update.yml index 81034b9c7..34a2de82f 100644 --- a/swagger/paths/application/team_members/update.yml +++ b/swagger/paths/application/team_members/update.yml @@ -5,31 +5,46 @@ summary: Update Agents in Team description: All agents except the one passed in params will be removed security: - userApiKey: [] -parameters: - - name: data - in: body - required: true - schema: - type: object - required: - - user_ids - properties: - user_ids: - type: array - items: - type: integer - description: IDs of users to be added to the team +requestBody: + required: true + content: + application/json: + schema: + type: object + required: + - user_ids + properties: + user_ids: + type: array + items: + type: integer + description: IDs of users to be added to the team + example: [1] responses: - 200: + '200': description: Success - schema: - type: array - description: 'Array of all agents in the team' - items: - $ref: '#/definitions/agent' - 404: - description: Team not found - 403: + content: + application/json: + schema: + type: array + description: 'Array of all agents in the team' + items: + $ref: '#/components/schemas/agent' + '403': description: Access denied - 422: + content: + application/json: + schema: + $ref: '#/components/schemas/bad_request_error' + '404': + description: Team not found + content: + application/json: + schema: + $ref: '#/components/schemas/bad_request_error' + '422': description: User must exist + content: + application/json: + schema: + $ref: '#/components/schemas/bad_request_error' diff --git a/swagger/paths/application/teams/create.yml b/swagger/paths/application/teams/create.yml index 2ec066738..b96068b11 100644 --- a/swagger/paths/application/teams/create.yml +++ b/swagger/paths/application/teams/create.yml @@ -2,18 +2,27 @@ tags: - Teams operationId: create-a-team summary: Create a team +security: + - userApiKey: [] description: Create a team in the account parameters: - - $ref: '#/parameters/account_id' - - name: data - in: body - required: true - schema: - $ref: '#/definitions/team_create_update_payload' + - $ref: '#/components/parameters/account_id' +requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/team_create_update_payload' responses: - 200: + '200': description: Success - schema: - $ref: '#/definitions/team' - 401: + content: + application/json: + schema: + $ref: '#/components/schemas/team' + '401': description: Unauthorized + content: + application/json: + schema: + $ref: '#/components/schemas/bad_request_error' diff --git a/swagger/paths/application/teams/delete.yml b/swagger/paths/application/teams/delete.yml index 7c5698b99..eae30ecba 100644 --- a/swagger/paths/application/teams/delete.yml +++ b/swagger/paths/application/teams/delete.yml @@ -2,11 +2,21 @@ tags: - Teams operationId: delete-a-team summary: Delete a team +security: + - userApiKey: [] description: Delete a team from the account responses: - 200: + '200': description: Success - 401: + '401': description: Unauthorized - 404: + content: + application/json: + schema: + $ref: '#/components/schemas/bad_request_error' + '404': description: The team does not exist in the account + content: + application/json: + schema: + $ref: '#/components/schemas/bad_request_error' diff --git a/swagger/paths/application/teams/index.yml b/swagger/paths/application/teams/index.yml index f93b6a553..85ac0ff8f 100644 --- a/swagger/paths/application/teams/index.yml +++ b/swagger/paths/application/teams/index.yml @@ -2,14 +2,22 @@ tags: - Teams operationId: list-all-teams summary: List all teams +security: + - userApiKey: [] description: List all teams available in the current account responses: - 200: + '200': description: Success - schema: - type: array - description: 'Array of teams' - items: - $ref: '#/definitions/team' - 401: + content: + application/json: + schema: + type: array + description: 'Array of teams' + items: + $ref: '#/components/schemas/team' + '401': description: Unauthorized + content: + application/json: + schema: + $ref: '#/components/schemas/bad_request_error' diff --git a/swagger/paths/application/teams/show.yml b/swagger/paths/application/teams/show.yml index 485b4730b..15869beda 100644 --- a/swagger/paths/application/teams/show.yml +++ b/swagger/paths/application/teams/show.yml @@ -2,13 +2,25 @@ tags: - Teams operationId: get-details-of-a-single-team summary: Get a team details +security: + - userApiKey: [] description: Get the details of a team in the account responses: - 200: + '200': description: Success - schema: - $ref: '#/definitions/team' - 401: + content: + application/json: + schema: + $ref: '#/components/schemas/team' + '401': description: Unauthorized - 404: + content: + application/json: + schema: + $ref: '#/components/schemas/bad_request_error' + '404': description: The given team ID does not exist in the account + content: + application/json: + schema: + $ref: '#/components/schemas/bad_request_error' diff --git a/swagger/paths/application/teams/update.yml b/swagger/paths/application/teams/update.yml index 8afeddc02..5b620e888 100644 --- a/swagger/paths/application/teams/update.yml +++ b/swagger/paths/application/teams/update.yml @@ -2,17 +2,25 @@ tags: - Teams operationId: update-a-team summary: Update a team +security: + - userApiKey: [] description: Update a team's attributes -parameters: - - name: data - in: body - required: true - schema: - $ref: '#/definitions/team_create_update_payload' +requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/team_create_update_payload' responses: - 200: + '200': description: Success - schema: - $ref: '#/definitions/team' - 401: + content: + application/json: + schema: + $ref: '#/components/schemas/team' + '401': description: Unauthorized + content: + application/json: + schema: + $ref: '#/components/schemas/bad_request_error' diff --git a/swagger/paths/application/webhooks/create.yml b/swagger/paths/application/webhooks/create.yml index e4d3053ee..81d7bbf1a 100644 --- a/swagger/paths/application/webhooks/create.yml +++ b/swagger/paths/application/webhooks/create.yml @@ -2,18 +2,27 @@ tags: - Webhooks operationId: create-a-webhook summary: Add a webhook +security: + - userApiKey: [] description: Add a webhook subscription to the account parameters: - - $ref: '#/parameters/account_id' - - name: data - in: body - required: true - schema: - $ref: '#/definitions/webhook_create_update_payload' + - $ref: '#/components/parameters/account_id' +requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/webhook_create_update_payload' responses: - 200: + '200': description: Success - schema: - $ref: '#/definitions/webhook' - 401: + content: + application/json: + schema: + $ref: '#/components/schemas/webhook' + '401': description: Unauthorized + content: + application/json: + schema: + $ref: '#/components/schemas/bad_request_error' diff --git a/swagger/paths/application/webhooks/delete.yml b/swagger/paths/application/webhooks/delete.yml index 525c22dfc..07cfdbaf8 100644 --- a/swagger/paths/application/webhooks/delete.yml +++ b/swagger/paths/application/webhooks/delete.yml @@ -2,11 +2,21 @@ tags: - Webhooks operationId: delete-a-webhook summary: Delete a webhook +security: + - userApiKey: [] description: Delete a webhook from the account responses: - 200: + '200': description: Success - 401: + '401': description: Unauthorized - 404: + content: + application/json: + schema: + type: object + '404': description: The webhook does not exist in the account + content: + application/json: + schema: + $ref: '#/components/schemas/bad_request_error' diff --git a/swagger/paths/application/webhooks/index.yml b/swagger/paths/application/webhooks/index.yml index f96fa00de..d385a9ad1 100644 --- a/swagger/paths/application/webhooks/index.yml +++ b/swagger/paths/application/webhooks/index.yml @@ -2,14 +2,22 @@ tags: - Webhooks operationId: list-all-webhooks summary: List all webhooks -description: List all webhooks in the account +security: + - userApiKey: [] +description: List all webhooks in the account responses: - 200: + '200': description: Success - schema: - type: array - description: 'Array of webhook objects' - items: - $ref: '#/definitions/webhook' - 401: + content: + application/json: + schema: + type: array + description: 'Array of webhook objects' + items: + $ref: '#/components/schemas/webhook' + '401': description: Unauthorized + content: + application/json: + schema: + $ref: '#/components/schemas/bad_request_error' diff --git a/swagger/paths/application/webhooks/update.yml b/swagger/paths/application/webhooks/update.yml index b395e7362..5e6a36782 100644 --- a/swagger/paths/application/webhooks/update.yml +++ b/swagger/paths/application/webhooks/update.yml @@ -2,18 +2,27 @@ tags: - Webhooks operationId: update-a-webhook summary: Update a webhook object +security: + - userApiKey: [] description: Update a webhook object in the account parameters: - - $ref: '#/parameters/account_id' - - name: data - in: body - required: true - schema: - $ref: '#/definitions/webhook_create_update_payload' + - $ref: '#/components/parameters/account_id' +requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/webhook_create_update_payload' responses: - 200: + '200': description: Success - schema: - $ref: '#/definitions/webhook' - 401: + content: + application/json: + schema: + $ref: '#/components/schemas/webhook' + '401': description: Unauthorized + content: + application/json: + schema: + $ref: '#/components/schemas/bad_request_error' diff --git a/swagger/paths/index.yml b/swagger/paths/index.yml index 769f38c27..379b2eef0 100644 --- a/swagger/paths/index.yml +++ b/swagger/paths/index.yml @@ -7,7 +7,7 @@ $ref: ./platform/accounts/create.yml /platform/api/v1/accounts/{account_id}: parameters: - - $ref: '#/parameters/account_id' + - $ref: '#/components/parameters/account_id' get: $ref: './platform/accounts/show.yml' patch: @@ -15,12 +15,11 @@ delete: $ref: ./platform/accounts/delete.yml - # Account Users /platform/api/v1/accounts/{account_id}/account_users: parameters: - - $ref: '#/parameters/account_id' + - $ref: '#/components/parameters/account_id' get: $ref: './platform/account_users/index.yml' post: @@ -37,7 +36,7 @@ $ref: ./platform/agent_bots/create.yml /platform/api/v1/agent_bots/{id}: parameters: - - $ref: '#/parameters/agent_bot_id' + - $ref: '#/components/parameters/agent_bot_id' get: $ref: './platform/agent_bots/show.yml' patch: @@ -52,7 +51,7 @@ $ref: ./platform/users/create.yml /platform/api/v1/users/{id}: parameters: - - $ref: '#/parameters/platform_user_id' + - $ref: '#/components/parameters/platform_user_id' get: $ref: './platform/users/show.yml' patch: @@ -61,11 +60,10 @@ $ref: ./platform/users/delete.yml /platform/api/v1/users/{id}/login: parameters: - - $ref: '#/parameters/platform_user_id' + - $ref: '#/components/parameters/platform_user_id' get: $ref: './platform/users/login.yml' - # ---------------- end of platform path -----------# # ------------ Public API routes ------------# @@ -74,7 +72,7 @@ /public/api/v1/inboxes/{inbox_identifier}: parameters: - - $ref: '#/parameters/public_inbox_identifier' + - $ref: '#/components/parameters/public_inbox_identifier' get: $ref: './public/inboxes/show.yml' @@ -82,13 +80,13 @@ /public/api/v1/inboxes/{inbox_identifier}/contacts: parameters: - - $ref: '#/parameters/public_inbox_identifier' + - $ref: '#/components/parameters/public_inbox_identifier' post: $ref: ./public/inboxes/contacts/create.yml /public/api/v1/inboxes/{inbox_identifier}/contacts/{contact_identifier}: parameters: - - $ref: '#/parameters/public_inbox_identifier' - - $ref: '#/parameters/public_contact_identifier' + - $ref: '#/components/parameters/public_inbox_identifier' + - $ref: '#/components/parameters/public_contact_identifier' get: $ref: './public/inboxes/contacts/show.yml' patch: @@ -96,8 +94,8 @@ /public/api/v1/inboxes/{inbox_identifier}/contacts/{contact_identifier}/conversations: parameters: - - $ref: '#/parameters/public_inbox_identifier' - - $ref: '#/parameters/public_contact_identifier' + - $ref: '#/components/parameters/public_inbox_identifier' + - $ref: '#/components/parameters/public_contact_identifier' post: $ref: ./public/inboxes/conversations/create.yml get: @@ -105,61 +103,54 @@ /public/api/v1/inboxes/{inbox_identifier}/contacts/{contact_identifier}/conversations/{conversation_id}: parameters: - - $ref: '#/parameters/public_inbox_identifier' - - $ref: '#/parameters/public_contact_identifier' - - $ref: '#/parameters/conversation_id' + - $ref: '#/components/parameters/public_inbox_identifier' + - $ref: '#/components/parameters/public_contact_identifier' + - $ref: '#/components/parameters/conversation_id' get: $ref: ./public/inboxes/conversations/show.yml /public/api/v1/inboxes/{inbox_identifier}/contacts/{contact_identifier}/conversations/{conversation_id}/toggle_status: parameters: - - $ref: '#/parameters/public_inbox_identifier' - - $ref: '#/parameters/public_contact_identifier' - - $ref: '#/parameters/conversation_id' + - $ref: '#/components/parameters/public_inbox_identifier' + - $ref: '#/components/parameters/public_contact_identifier' + - $ref: '#/components/parameters/conversation_id' post: $ref: ./public/inboxes/conversations/toggle_status.yml /public/api/v1/inboxes/{inbox_identifier}/contacts/{contact_identifier}/conversations/{conversation_id}/toggle_typing: parameters: - - $ref: '#/parameters/public_inbox_identifier' - - $ref: '#/parameters/public_contact_identifier' - - $ref: '#/parameters/conversation_id' + - $ref: '#/components/parameters/public_inbox_identifier' + - $ref: '#/components/parameters/public_contact_identifier' + - $ref: '#/components/parameters/conversation_id' post: $ref: ./public/inboxes/conversations/toggle_typing.yml /public/api/v1/inboxes/{inbox_identifier}/contacts/{contact_identifier}/conversations/{conversation_id}/update_last_seen: parameters: - - $ref: '#/parameters/public_inbox_identifier' - - $ref: '#/parameters/public_contact_identifier' - - $ref: '#/parameters/conversation_id' + - $ref: '#/components/parameters/public_inbox_identifier' + - $ref: '#/components/parameters/public_contact_identifier' + - $ref: '#/components/parameters/conversation_id' post: $ref: ./public/inboxes/conversations/update_last_seen.yml /public/api/v1/inboxes/{inbox_identifier}/contacts/{contact_identifier}/conversations/{conversation_id}/messages: parameters: - - $ref: '#/parameters/public_inbox_identifier' - - $ref: '#/parameters/public_contact_identifier' - - $ref: '#/parameters/conversation_id' + - $ref: '#/components/parameters/public_inbox_identifier' + - $ref: '#/components/parameters/public_contact_identifier' + - $ref: '#/components/parameters/conversation_id' post: $ref: ./public/inboxes/messages/create.yml get: $ref: ./public/inboxes/messages/index.yml + /public/api/v1/inboxes/{inbox_identifier}/contacts/{contact_identifier}/conversations/{conversation_id}/messages/{message_id}: parameters: - - $ref: '#/parameters/public_inbox_identifier' - - $ref: '#/parameters/public_contact_identifier' - - $ref: '#/parameters/conversation_id' - - $ref: '#/parameters/message_id' + - $ref: '#/components/parameters/public_inbox_identifier' + - $ref: '#/components/parameters/public_contact_identifier' + - $ref: '#/components/parameters/conversation_id' + - $ref: '#/components/parameters/message_id' patch: $ref: ./public/inboxes/messages/update.yml -/api/v1/accounts/{account_id}/contacts/{contact_identifier}/labels: - parameters: - - $ref: '#/parameters/account_id' - - $ref: '#/parameters/public_contact_identifier' - get: - $ref: ./application/contacts/labels/index.yml - post: - $ref: ./application/contacts/labels/create.yml # ---------------- end of public api routes-----------# @@ -167,7 +158,7 @@ /survey/responses/{conversation_uuid}: parameters: - - $ref: '#/parameters/conversation_uuid' + - $ref: '#/components/parameters/conversation_uuid' get: $ref: ./survey/show.yml @@ -175,19 +166,18 @@ # ------------ Application API routes ------------# - # AgentBots /api/v1/accounts/{account_id}/agent_bots: parameters: - - $ref: '#/parameters/account_id' + - $ref: '#/components/parameters/account_id' get: $ref: ./application/agent_bots/index.yml post: $ref: ./application/agent_bots/create.yml /api/v1/accounts/{account_id}/agent_bots/{id}: parameters: - - $ref: '#/parameters/account_id' - - $ref: '#/parameters/agent_bot_id' + - $ref: '#/components/parameters/account_id' + - $ref: '#/components/parameters/agent_bot_id' get: $ref: './application/agent_bots/show.yml' patch: @@ -198,14 +188,14 @@ # Agents /api/v1/accounts/{account_id}/agents: parameters: - - $ref: '#/parameters/account_id' + - $ref: '#/components/parameters/account_id' get: $ref: ./application/agents/index.yml post: $ref: ./application/agents/create.yml /api/v1/accounts/{account_id}/agents/{id}: parameters: - - $ref: '#/parameters/account_id' + - $ref: '#/components/parameters/account_id' patch: $ref: ./application/agents/update.yml delete: @@ -214,14 +204,14 @@ # Canned Responses /api/v1/accounts/{account_id}/canned_responses: parameters: - - $ref: '#/parameters/account_id' + - $ref: '#/components/parameters/account_id' get: $ref: ./application/canned_responses/index.yml post: $ref: ./application/canned_responses/create.yml /api/v1/accounts/{account_id}/canned_responses/{id}: parameters: - - $ref: '#/parameters/account_id' + - $ref: '#/components/parameters/account_id' patch: $ref: ./application/canned_responses/update.yml delete: @@ -230,17 +220,18 @@ # Custom Attributes /api/v1/accounts/{account_id}/custom_attribute_definitions: parameters: - - $ref: '#/parameters/account_id' + - $ref: '#/components/parameters/account_id' get: $ref: ./application/custom_attributes/index.yml post: $ref: ./application/custom_attributes/create.yml /api/v1/accounts/{account_id}/custom_attribute_definitions/{id}: parameters: - - $ref: '#/parameters/account_id' + - $ref: '#/components/parameters/account_id' - name: id in: path - type: number + schema: + type: number description: ID of the custom attribute required: true get: @@ -257,13 +248,12 @@ $ref: ./application/contacts/crud.yml /api/v1/accounts/{account_id}/contacts/{id}/conversations: $ref: ./application/contacts/conversations.yml +/api/v1/accounts/{account_id}/contacts/{id}/labels: + $ref: ./application/contacts/labels.yml /api/v1/accounts/{account_id}/contacts/search: $ref: ./application/contacts/search.yml /api/v1/accounts/{account_id}/contacts/filter: - parameters: - - $ref: '#/parameters/account_id' - post: - $ref: ./application/contacts/filter.yml + $ref: ./application/contacts/filter.yml /api/v1/accounts/{account_id}/contacts/{id}/contact_inboxes: $ref: ./application/contact_inboxes/create.yml /api/v1/accounts/{account_id}/contacts/{id}/contactable_inboxes: @@ -273,17 +263,18 @@ # Automation Rule /api/v1/accounts/{account_id}/automation_rules: parameters: - - $ref: '#/parameters/account_id' + - $ref: '#/components/parameters/account_id' get: $ref: ./application/automation_rule/index.yml post: $ref: ./application/automation_rule/create.yml /api/v1/accounts/{account_id}/automation_rules/{id}: parameters: - - $ref: '#/parameters/account_id' + - $ref: '#/components/parameters/account_id' - name: id in: path - type: number + schema: + type: number description: ID of the Automation Rule required: true get: @@ -293,36 +284,37 @@ delete: $ref: ./application/automation_rule/delete.yml - # Help Center /api/v1/accounts/{account_id}/portals: parameters: - - $ref: '#/parameters/account_id' + - $ref: '#/components/parameters/account_id' post: $ref: ./application/portal/create.yml get: $ref: ./application/portal/index.yml +/api/v1/accounts/{account_id}/portals/{id}: + parameters: + - $ref: '#/components/parameters/account_id' + - $ref: '#/components/parameters/portal_id' patch: $ref: ./application/portal/update.yml - # Help Center category -/api/v1/accounts/{account_id}/portals/{portal_id}/categories: +/api/v1/accounts/{account_id}/portals/{id}/categories: parameters: - - $ref: '#/parameters/account_id' - - $ref: '#/parameters/portal_id' + - $ref: '#/components/parameters/account_id' + - $ref: '#/components/parameters/portal_id' post: $ref: ./application/category/create.yml # Help Center article -/api/v1/accounts/{account_id}/portals/{portal_id}/articles: +/api/v1/accounts/{account_id}/portals/{id}/articles: parameters: - - $ref: '#/parameters/account_id' - - $ref: '#/parameters/portal_id' + - $ref: '#/components/parameters/account_id' + - $ref: '#/components/parameters/portal_id' post: $ref: ./application/article/create.yml - # Conversations /api/v1/accounts/{account_id}/conversations/meta: $ref: ./application/conversation/meta.yml @@ -330,34 +322,34 @@ $ref: ./application/conversation/index.yml /api/v1/accounts/{account_id}/conversations/filter: parameters: - - $ref: '#/parameters/account_id' + - $ref: '#/components/parameters/account_id' post: $ref: ./application/conversation/filter.yml /api/v1/accounts/{account_id}/conversations/{conversation_id}: parameters: - - $ref: '#/parameters/account_id' - - $ref: '#/parameters/conversation_id' + - $ref: '#/components/parameters/account_id' + - $ref: '#/components/parameters/conversation_id' get: $ref: ./application/conversation/show.yml patch: $ref: ./application/conversation/update.yml /api/v1/accounts/{account_id}/conversations/{conversation_id}/toggle_status: parameters: - - $ref: '#/parameters/account_id' - - $ref: '#/parameters/conversation_id' + - $ref: '#/components/parameters/account_id' + - $ref: '#/components/parameters/conversation_id' post: $ref: ./application/conversation/toggle_status.yml /api/v1/accounts/{account_id}/conversations/{conversation_id}/toggle_priority: parameters: - - $ref: '#/parameters/account_id' - - $ref: '#/parameters/conversation_id' + - $ref: '#/components/parameters/account_id' + - $ref: '#/components/parameters/conversation_id' post: $ref: ./application/conversation/toggle_priority.yml /api/v1/accounts/{account_id}/conversations/{conversation_id}/custom_attributes: parameters: - - $ref: '#/parameters/account_id' - - $ref: '#/parameters/conversation_id' + - $ref: '#/components/parameters/account_id' + - $ref: '#/components/parameters/conversation_id' post: $ref: ./application/conversation/custom_attributes.yml @@ -365,8 +357,8 @@ /api/v1/accounts/{account_id}/conversations/{conversation_id}/assignments: parameters: - - $ref: '#/parameters/account_id' - - $ref: '#/parameters/conversation_id' + - $ref: '#/components/parameters/account_id' + - $ref: '#/components/parameters/conversation_id' post: $ref: ./application/conversation/assignments.yml @@ -374,14 +366,13 @@ /api/v1/accounts/{account_id}/conversations/{conversation_id}/labels: parameters: - - $ref: '#/parameters/account_id' - - $ref: '#/parameters/conversation_id' + - $ref: '#/components/parameters/account_id' + - $ref: '#/components/parameters/conversation_id' get: $ref: ./application/conversation/labels/index.yml post: $ref: ./application/conversation/labels/create.yml - # Inboxes /api/v1/accounts/{account_id}/inboxes: $ref: ./application/inboxes/index.yml @@ -399,14 +390,14 @@ # Inbox Members /api/v1/accounts/{account_id}/inbox_members/{inbox_id}: parameters: - - $ref: '#/parameters/account_id' - - $ref: '#/parameters/inbox_id' + - $ref: '#/components/parameters/account_id' + - $ref: '#/components/parameters/inbox_id' get: $ref: ./application/inboxes/inbox_members/show.yml /api/v1/accounts/{account_id}/inbox_members: parameters: - - $ref: '#/parameters/account_id' + - $ref: '#/components/parameters/account_id' post: $ref: ./application/inboxes/inbox_members/create.yml patch: @@ -414,31 +405,27 @@ delete: $ref: ./application/inboxes/inbox_members/delete.yml - - # Messages /api/v1/accounts/{account_id}/conversations/{conversation_id}/messages: parameters: - - $ref: '#/parameters/account_id' - - $ref: '#/parameters/conversation_id' + - $ref: '#/components/parameters/account_id' + - $ref: '#/components/parameters/conversation_id' get: $ref: ./application/conversation/messages/index.yml post: $ref: ./application/conversation/messages/create.yml /api/v1/accounts/{account_id}/conversations/{conversation_id}/messages/{message_id}: parameters: - - $ref: '#/parameters/account_id' - - $ref: '#/parameters/conversation_id' - - $ref: '#/parameters/message_id' + - $ref: '#/components/parameters/account_id' + - $ref: '#/components/parameters/conversation_id' + - $ref: '#/components/parameters/message_id' delete: $ref: ./application/conversation/messages/delete.yml - - # Integrations /api/v1/accounts/{account_id}/integrations/apps: parameters: - - $ref: '#/parameters/account_id' + - $ref: '#/components/parameters/account_id' get: $ref: './application/integrations/apps/show.yml' /api/v1/accounts/{account_id}/integrations/hooks: @@ -450,25 +437,22 @@ delete: $ref: ./application/integrations/hooks/delete.yml - - # Profile /api/v1/profile: $ref: ./profile/index.yml - # Teams /api/v1/accounts/{account_id}/teams: parameters: - - $ref: '#/parameters/account_id' + - $ref: '#/components/parameters/account_id' get: $ref: ./application/teams/index.yml post: $ref: ./application/teams/create.yml /api/v1/accounts/{account_id}/teams/{team_id}: parameters: - - $ref: '#/parameters/account_id' - - $ref: '#/parameters/team_id' + - $ref: '#/components/parameters/account_id' + - $ref: '#/components/parameters/team_id' get: $ref: './application/teams/show.yml' patch: @@ -477,8 +461,8 @@ $ref: ./application/teams/delete.yml /api/v1/accounts/{account_id}/teams/{team_id}/team_members: parameters: - - $ref: '#/parameters/account_id' - - $ref: '#/parameters/team_id' + - $ref: '#/components/parameters/account_id' + - $ref: '#/components/parameters/team_id' get: $ref: ./application/team_members/index.yml post: @@ -493,11 +477,12 @@ # Custom Filters /api/v1/accounts/{account_id}/custom_filters: parameters: - - $ref: '#/parameters/account_id' + - $ref: '#/components/parameters/account_id' - in: query name: filter_type - type: string - enum: ['conversation', 'contact', 'report'] + schema: + type: string + enum: ['conversation', 'contact', 'report'] required: false description: The type of custom filter get: @@ -506,8 +491,8 @@ $ref: ./application/custom_filters/create.yml /api/v1/accounts/{account_id}/custom_filters/{custom_filter_id}: parameters: - - $ref: '#/parameters/account_id' - - $ref: '#/parameters/custom_filter_id' + - $ref: '#/components/parameters/account_id' + - $ref: '#/components/parameters/custom_filter_id' get: $ref: './application/custom_filters/show.yml' patch: @@ -518,15 +503,15 @@ # webhooks /api/v1/accounts/{account_id}/webhooks: parameters: - - $ref: '#/parameters/account_id' + - $ref: '#/components/parameters/account_id' get: $ref: ./application/webhooks/index.yml post: $ref: ./application/webhooks/create.yml /api/v1/accounts/{account_id}/webhooks/{webhook_id}: parameters: - - $ref: '#/parameters/account_id' - - $ref: '#/parameters/webhook_id' + - $ref: '#/components/parameters/account_id' + - $ref: '#/components/parameters/webhook_id' patch: $ref: ./application/webhooks/update.yml delete: @@ -537,20 +522,23 @@ # List /api/v2/accounts/{account_id}/reports: parameters: - - $ref: '#/parameters/account_id' - - $ref: '#/parameters/report_metric' - - $ref: '#/parameters/report_type' + - $ref: '#/components/parameters/account_id' + - $ref: '#/components/parameters/report_metric' + - $ref: '#/components/parameters/report_type' - in: query name: id - type: string + schema: + type: string description: The Id of specific object in case of agent/inbox/label - in: query name: since - type: string + schema: + type: string description: The timestamp from where report should start. - in: query name: until - type: string + schema: + type: string description: The timestamp from where report should stop. get: $ref: './application/reports/index.yml' @@ -558,19 +546,22 @@ # Summary /api/v2/accounts/{account_id}/reports/summary: parameters: - - $ref: '#/parameters/account_id' - - $ref: '#/parameters/report_type' + - $ref: '#/components/parameters/account_id' + - $ref: '#/components/parameters/report_type' - in: query name: id - type: string + schema: + type: string description: The Id of specific object in case of agent/inbox/label - in: query name: since - type: string + schema: + type: string description: The timestamp from where report should start. - in: query name: until - type: string + schema: + type: string description: The timestamp from where report should stop. get: $ref: './application/reports/summary.yml' @@ -578,12 +569,13 @@ # Conversation metrics for account /api/v2/accounts/{account_id}/reports/conversations: parameters: - - $ref: '#/parameters/account_id' + - $ref: '#/components/parameters/account_id' - in: query name: type - type: string - enum: - - account + schema: + type: string + enum: + - account required: true description: Type of report get: @@ -592,17 +584,43 @@ # Conversation metrics for agent /api/v2/accounts/{account_id}/reports/conversations/: parameters: - - $ref: '#/parameters/account_id' + - $ref: '#/components/parameters/account_id' - in: query name: type - type: string - enum: - - agent + schema: + type: string + enum: + - agent required: true description: Type of report - in: query name: user_id - type: string + schema: + type: string description: The numeric ID of the user get: $ref: './application/reports/conversation/agent.yml' + +# Conversations Messages +/accounts/{account_id}/conversations/{conversation_id}/messages: + parameters: + - $ref: '#/components/parameters/account_id' + - name: conversation_id + in: path + description: ID of the conversation + required: true + schema: + type: number + get: + tags: + - Conversation + summary: Get messages from a conversation + description: Returns all messages from a specific conversation + operationId: getConversationMessages + responses: + '200': + description: Success + content: + application/json: + schema: + $ref: '#/components/schemas/conversation_messages' diff --git a/swagger/paths/platform/account_users/create.yml b/swagger/paths/platform/account_users/create.yml index 97dc7127d..69d7c4b98 100644 --- a/swagger/paths/platform/account_users/create.yml +++ b/swagger/paths/platform/account_users/create.yml @@ -5,37 +5,33 @@ summary: Create an Account User description: Create an Account User security: - platformAppApiKey: [] -parameters: - - name: data - in: body - required: true - schema: - type: object - required: - - user_id - - role - properties: - user_id: - type: integer - description: The ID of the user - role: - type: string - description: whether user is an administrator or agent - +requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/account_user_create_update_payload' responses: - 200: + '200': description: Success - schema: - properties: - account_id: - type: integer - description: The ID of the user - user_id: - type: integer - description: The ID of the user - role: - type: string - description: whether user is an administrator or agent - - 401: + content: + application/json: + schema: + type: object + properties: + account_id: + type: integer + description: The ID of the account + user_id: + type: integer + description: The ID of the user + role: + type: string + description: whether user is an administrator or agent + + '401': description: Unauthorized + content: + application/json: + schema: + $ref: '#/components/schemas/bad_request_error' diff --git a/swagger/paths/platform/account_users/delete.yml b/swagger/paths/platform/account_users/delete.yml index d864619f7..f2fc35535 100644 --- a/swagger/paths/platform/account_users/delete.yml +++ b/swagger/paths/platform/account_users/delete.yml @@ -5,23 +5,18 @@ summary: Delete an Account User description: Delete an Account User security: - platformAppApiKey: [] -parameters: - - name: data - in: body - required: true - schema: - type: object - required: - - user_id - properties: - user_id: - type: integer - description: The ID of the user - responses: - 200: + '200': description: Success - 401: + '401': description: Unauthorized - 404: + content: + application/json: + schema: + $ref: '#/components/schemas/bad_request_error' + '404': description: The account does not exist + content: + application/json: + schema: + $ref: '#/components/schemas/bad_request_error' diff --git a/swagger/paths/platform/account_users/index.yml b/swagger/paths/platform/account_users/index.yml index bcaed1807..dfcdded99 100644 --- a/swagger/paths/platform/account_users/index.yml +++ b/swagger/paths/platform/account_users/index.yml @@ -6,23 +6,15 @@ description: List all account users security: - platformAppApiKey: [] responses: - 200: + '200': description: Success - schema: - type: array - description: 'Array of account users' - items: - type: object - properties: - account_id: - type: integer - description: The ID of the user - user_id: - type: integer - description: The ID of the user - role: - type: string - description: whether user is an administrator or agent - - 401: + content: + application/json: + schema: + $ref: '#/components/schemas/account_user' + '401': description: Unauthorized + content: + application/json: + schema: + $ref: '#/components/schemas/bad_request_error' diff --git a/swagger/paths/platform/accounts/create.yml b/swagger/paths/platform/accounts/create.yml index a6e9841b9..a8ea455f5 100644 --- a/swagger/paths/platform/accounts/create.yml +++ b/swagger/paths/platform/accounts/create.yml @@ -5,16 +5,22 @@ summary: Create an Account description: Create an Account security: - platformAppApiKey: [] -parameters: - - name: data - in: body - required: true - schema: - $ref: '#/definitions/account_create_update_payload' +requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/account_create_update_payload' responses: 200: description: Success - schema: - $ref: '#/definitions/platform_account' + content: + application/json: + schema: + $ref: '#/components/schemas/platform_account' 401: description: Unauthorized + content: + application/json: + schema: + $ref: '#/components/schemas/bad_request_error' diff --git a/swagger/paths/platform/accounts/delete.yml b/swagger/paths/platform/accounts/delete.yml index 755b76d1e..d7e25795b 100644 --- a/swagger/paths/platform/accounts/delete.yml +++ b/swagger/paths/platform/accounts/delete.yml @@ -6,9 +6,17 @@ description: Delete an Account security: - platformAppApiKey: [] responses: - 200: + '200': description: Success - 401: + '401': description: Unauthorized - 404: + content: + application/json: + schema: + $ref: '#/components/schemas/bad_request_error' + '404': description: The account does not exist + content: + application/json: + schema: + $ref: '#/components/schemas/bad_request_error' diff --git a/swagger/paths/platform/accounts/show.yml b/swagger/paths/platform/accounts/show.yml index d8ec94fe6..1df201701 100644 --- a/swagger/paths/platform/accounts/show.yml +++ b/swagger/paths/platform/accounts/show.yml @@ -6,11 +6,21 @@ description: Get the details of an account security: - platformAppApiKey: [] responses: - 200: + '200': description: Success - schema: - $ref: '#/definitions/platform_account' - 401: + content: + application/json: + schema: + $ref: '#/components/schemas/platform_account' + '401': description: Unauthorized - 404: + content: + application/json: + schema: + $ref: '#/components/schemas/bad_request_error' + '404': description: The given account does not exist + content: + application/json: + schema: + $ref: '#/components/schemas/bad_request_error' diff --git a/swagger/paths/platform/accounts/update.yml b/swagger/paths/platform/accounts/update.yml index a70ce9468..a30caea1f 100644 --- a/swagger/paths/platform/accounts/update.yml +++ b/swagger/paths/platform/accounts/update.yml @@ -5,16 +5,22 @@ summary: Update an account description: Update an account's attributes security: - platformAppApiKey: [] -parameters: - - name: data - in: body - required: true - schema: - $ref: '#/definitions/account_create_update_payload' +requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/account_create_update_payload' responses: 200: description: Success - schema: - $ref: '#/definitions/platform_account' + content: + application/json: + schema: + $ref: '#/components/schemas/platform_account' 401: description: Unauthorized + content: + application/json: + schema: + $ref: '#/components/schemas/bad_request_error' diff --git a/swagger/paths/platform/agent_bots/create.yml b/swagger/paths/platform/agent_bots/create.yml index a862ca131..4f916d672 100644 --- a/swagger/paths/platform/agent_bots/create.yml +++ b/swagger/paths/platform/agent_bots/create.yml @@ -5,16 +5,22 @@ summary: Create an Agent Bot description: Create an agent bot security: - platformAppApiKey: [] -parameters: - - name: data - in: body - required: true - schema: - $ref: '#/definitions/agent_bot_create_update_payload' +requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/platform_agent_bot_create_update_payload' responses: - 200: + '200': description: Success - schema: - $ref: '#/definitions/agent_bot' - 401: + content: + application/json: + schema: + $ref: '#/components/schemas/agent_bot' + '401': description: Unauthorized + content: + application/json: + schema: + $ref: '#/components/schemas/bad_request_error' diff --git a/swagger/paths/platform/agent_bots/delete.yml b/swagger/paths/platform/agent_bots/delete.yml index 676c8f525..a89fed307 100644 --- a/swagger/paths/platform/agent_bots/delete.yml +++ b/swagger/paths/platform/agent_bots/delete.yml @@ -6,9 +6,17 @@ description: Delete an AgentBot security: - platformAppApiKey: [] responses: - 200: + '200': description: Success - 401: + '401': description: Unauthorized - 404: + content: + application/json: + schema: + $ref: '#/components/schemas/bad_request_error' + '404': description: The agent bot does not exist + content: + application/json: + schema: + $ref: '#/components/schemas/bad_request_error' diff --git a/swagger/paths/platform/agent_bots/index.yml b/swagger/paths/platform/agent_bots/index.yml index 7ef3ee9c9..054085471 100644 --- a/swagger/paths/platform/agent_bots/index.yml +++ b/swagger/paths/platform/agent_bots/index.yml @@ -6,12 +6,18 @@ description: List all agent bots available security: - platformAppApiKey: [] responses: - 200: + '200': description: Success - schema: - type: array - description: 'Array of agent bots' - items: - $ref: '#/definitions/agent_bot' - 401: + content: + application/json: + schema: + type: array + description: 'Array of agent bots' + items: + $ref: '#/components/schemas/agent_bot' + '401': description: Unauthorized + content: + application/json: + schema: + $ref: '#/components/schemas/bad_request_error' diff --git a/swagger/paths/platform/agent_bots/show.yml b/swagger/paths/platform/agent_bots/show.yml index dfbd43a27..d0a8ba37c 100644 --- a/swagger/paths/platform/agent_bots/show.yml +++ b/swagger/paths/platform/agent_bots/show.yml @@ -6,11 +6,21 @@ description: Get the details of an agent bot security: - platformAppApiKey: [] responses: - 200: + '200': description: Success - schema: - $ref: '#/definitions/agent_bot' - 401: + content: + application/json: + schema: + $ref: '#/components/schemas/agent_bot' + '401': description: Unauthorized - 404: + content: + application/json: + schema: + $ref: '#/components/schemas/bad_request_error' + '404': description: The given agent bot ID does not exist + content: + application/json: + schema: + $ref: '#/components/schemas/bad_request_error' diff --git a/swagger/paths/platform/agent_bots/update.yml b/swagger/paths/platform/agent_bots/update.yml index 842b4854d..7abc46ab8 100644 --- a/swagger/paths/platform/agent_bots/update.yml +++ b/swagger/paths/platform/agent_bots/update.yml @@ -5,16 +5,22 @@ summary: Update an agent bot description: Update an agent bot's attributes security: - platformAppApiKey: [] -parameters: - - name: data - in: body - required: true - schema: - $ref: '#/definitions/agent_bot_create_update_payload' +requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/platform_agent_bot_create_update_payload' responses: - 200: + '200': description: Success - schema: - $ref: '#/definitions/agent_bot' - 401: + content: + application/json: + schema: + $ref: '#/components/schemas/agent_bot' + '401': description: Unauthorized + content: + application/json: + schema: + $ref: '#/components/schemas/bad_request_error' diff --git a/swagger/paths/platform/users/create.yml b/swagger/paths/platform/users/create.yml index 7433b8dc6..3d2bdb412 100644 --- a/swagger/paths/platform/users/create.yml +++ b/swagger/paths/platform/users/create.yml @@ -5,16 +5,22 @@ summary: Create a User description: Create a User security: - platformAppApiKey: [] -parameters: - - name: data - in: body - required: true - schema: - $ref: '#/definitions/user_create_update_payload' +requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/user_create_update_payload' responses: - 200: + '200': description: Success - schema: - $ref: '#/definitions/user' - 401: + content: + application/json: + schema: + $ref: '#/components/schemas/user' + '401': description: Unauthorized + content: + application/json: + schema: + $ref: '#/components/schemas/bad_request_error' diff --git a/swagger/paths/platform/users/delete.yml b/swagger/paths/platform/users/delete.yml index a7658c215..d26e9d285 100644 --- a/swagger/paths/platform/users/delete.yml +++ b/swagger/paths/platform/users/delete.yml @@ -6,9 +6,17 @@ description: Delete a User security: - platformAppApiKey: [] responses: - 200: + '200': description: Success - 401: + '401': description: Unauthorized - 404: + content: + application/json: + schema: + $ref: '#/components/schemas/bad_request_error' + '404': description: The user does not exist + content: + application/json: + schema: + $ref: '#/components/schemas/bad_request_error' diff --git a/swagger/paths/platform/users/login.yml b/swagger/paths/platform/users/login.yml index be210c7aa..9e327e745 100644 --- a/swagger/paths/platform/users/login.yml +++ b/swagger/paths/platform/users/login.yml @@ -6,15 +6,25 @@ description: Get the sso link of a user security: - platformAppApiKey: [] responses: - 200: + '200': description: Success - schema: - type: object - properties: - url: - type: string - description: SSO url to autenticate the user - 401: + content: + application/json: + schema: + type: object + properties: + url: + type: string + description: SSO url to autenticate the user + '401': description: Unauthorized - 404: + content: + application/json: + schema: + $ref: '#/components/schemas/bad_request_error' + '404': description: The given user does not exist + content: + application/json: + schema: + $ref: '#/components/schemas/bad_request_error' diff --git a/swagger/paths/platform/users/show.yml b/swagger/paths/platform/users/show.yml index bf937b49a..e42b3854e 100644 --- a/swagger/paths/platform/users/show.yml +++ b/swagger/paths/platform/users/show.yml @@ -6,11 +6,21 @@ description: Get the details of an user security: - platformAppApiKey: [] responses: - 200: + '200': description: Success - schema: - $ref: '#/definitions/user' - 401: + content: + application/json: + schema: + $ref: '#/components/schemas/user' + '401': description: Unauthorized - 404: + content: + application/json: + schema: + $ref: '#/components/schemas/bad_request_error' + '404': description: The given user does not exist + content: + application/json: + schema: + $ref: '#/components/schemas/bad_request_error' diff --git a/swagger/paths/platform/users/token.yml b/swagger/paths/platform/users/token.yml new file mode 100644 index 000000000..f4fedc311 --- /dev/null +++ b/swagger/paths/platform/users/token.yml @@ -0,0 +1,42 @@ +tags: + - Users +operationId: post-user-token +summary: Get User Access Token +description: Get the access token of a user +security: + - platformAppApiKey: [] +responses: + 200: + description: Success + schema: + type: object + properties: + access_token: + type: string + description: Access token of the user + expiry: + type: [integer, "null"] + description: Expiry timestamp + user: + type: object + properties: + id: + type: integer + description: User ID + name: + type: string + description: User's full name + display_name: + type: string + description: User's display name + email: + type: string + description: User's email address + pubsub_token: + type: string + description: User's pubsub token + 401: + description: Unauthorized + 404: + description: The given user does not exist + diff --git a/swagger/paths/platform/users/update.yml b/swagger/paths/platform/users/update.yml index 56ea0026d..f340448da 100644 --- a/swagger/paths/platform/users/update.yml +++ b/swagger/paths/platform/users/update.yml @@ -5,16 +5,22 @@ summary: Update a user description: Update a user's attributes security: - platformAppApiKey: [] -parameters: - - name: data - in: body - required: true - schema: - $ref: '#/definitions/user_create_update_payload' +requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/user_create_update_payload' responses: - 200: + '200': description: Success - schema: - $ref: '#/definitions/user' - 401: + content: + application/json: + schema: + $ref: '#/components/schemas/user' + '401': description: Unauthorized + content: + application/json: + schema: + $ref: '#/components/schemas/bad_request_error' diff --git a/swagger/paths/profile/index.yml b/swagger/paths/profile/index.yml index e20004cac..17eda92d0 100644 --- a/swagger/paths/profile/index.yml +++ b/swagger/paths/profile/index.yml @@ -4,10 +4,18 @@ get: operationId: fetchProfile summary: Fetch user profile description: Get the user profile details + security: + - userApiKey: [] responses: - 200: + '200': description: Success - schema: - $ref: '#/definitions/user' - 401: + content: + application/json: + schema: + $ref: '#/components/schemas/user' + '401': description: Unauthorized + content: + application/json: + schema: + $ref: '#/components/schemas/bad_request_error' diff --git a/swagger/paths/public/inboxes/contacts/create.yml b/swagger/paths/public/inboxes/contacts/create.yml index 51d894f3e..4e8d1f6eb 100644 --- a/swagger/paths/public/inboxes/contacts/create.yml +++ b/swagger/paths/public/inboxes/contacts/create.yml @@ -4,16 +4,22 @@ operationId: create-a-contact summary: Create a contact description: Create a contact security: [] -parameters: - - name: data - in: body - required: true - schema: - $ref: '#/definitions/public_contact_create_update_payload' +requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/public_contact_create_update_payload' responses: - 200: + '200': description: Success - schema: - $ref: '#/definitions/public_contact' - 401: + content: + application/json: + schema: + $ref: '#/components/schemas/public_contact' + '401': description: Unauthorized + content: + application/json: + schema: + $ref: '#/components/schemas/bad_request_error' diff --git a/swagger/paths/public/inboxes/contacts/show.yml b/swagger/paths/public/inboxes/contacts/show.yml index a5d8b8fdc..49039266a 100644 --- a/swagger/paths/public/inboxes/contacts/show.yml +++ b/swagger/paths/public/inboxes/contacts/show.yml @@ -5,11 +5,21 @@ summary: Get a contact description: Get the details of a contact security: [] responses: - 200: + '200': description: Success - schema: - $ref: '#/definitions/public_contact' - 401: + content: + application/json: + schema: + $ref: '#/components/schemas/public_contact' + '401': description: Unauthorized - 404: + content: + application/json: + schema: + $ref: '#/components/schemas/bad_request_error' + '404': description: The given contact does not exist + content: + application/json: + schema: + $ref: '#/components/schemas/bad_request_error' diff --git a/swagger/paths/public/inboxes/contacts/update.yml b/swagger/paths/public/inboxes/contacts/update.yml index a413c76b3..8134d7bfd 100644 --- a/swagger/paths/public/inboxes/contacts/update.yml +++ b/swagger/paths/public/inboxes/contacts/update.yml @@ -4,16 +4,22 @@ operationId: update-a-contact summary: Update a contact description: Update a contact's attributes security: [] -parameters: - - name: data - in: body - required: true - schema: - $ref: '#/definitions/public_contact_create_update_payload' +requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/public_contact_create_update_payload' responses: - 200: + '200': description: Success - schema: - $ref: '#/definitions/public_contact' - 401: + content: + application/json: + schema: + $ref: '#/components/schemas/public_contact' + '401': description: Unauthorized + content: + application/json: + schema: + $ref: '#/components/schemas/bad_request_error' diff --git a/swagger/paths/public/inboxes/conversations/create.yml b/swagger/paths/public/inboxes/conversations/create.yml index 4c2a85d22..31bd9ac86 100644 --- a/swagger/paths/public/inboxes/conversations/create.yml +++ b/swagger/paths/public/inboxes/conversations/create.yml @@ -4,16 +4,22 @@ operationId: create-a-conversation summary: Create a conversation description: Create a conversation security: [] -parameters: - - name: data - in: body - required: true - schema: - $ref: '#/definitions/public_conversation_create_payload' +requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/public_conversation_create_payload' responses: - 200: + '200': description: Success - schema: - $ref: '#/definitions/public_conversation' - 401: + content: + application/json: + schema: + $ref: '#/components/schemas/public_conversation' + '401': description: Unauthorized + content: + application/json: + schema: + $ref: '#/components/schemas/bad_request_error' diff --git a/swagger/paths/public/inboxes/conversations/index.yml b/swagger/paths/public/inboxes/conversations/index.yml index bcd598758..3bfc89873 100644 --- a/swagger/paths/public/inboxes/conversations/index.yml +++ b/swagger/paths/public/inboxes/conversations/index.yml @@ -3,13 +3,20 @@ tags: operationId: list-all-contact-conversations summary: List all conversations description: List all conversations for the contact +security: [] responses: - 200: + '200': description: Success - schema: - type: array - description: 'Array of conversations' - items: - $ref: '#/definitions/public_conversation' - 401: + content: + application/json: + schema: + type: array + description: 'Array of conversations' + items: + $ref: '#/components/schemas/public_conversation' + '401': description: Unauthorized + content: + application/json: + schema: + $ref: '#/components/schemas/bad_request_error' diff --git a/swagger/paths/public/inboxes/conversations/show.yml b/swagger/paths/public/inboxes/conversations/show.yml index 2ec6caa63..320457c48 100644 --- a/swagger/paths/public/inboxes/conversations/show.yml +++ b/swagger/paths/public/inboxes/conversations/show.yml @@ -3,12 +3,23 @@ tags: operationId: get-single-conversation summary: Get a single conversation description: Retrieves the details of a specific conversation +security: [] responses: - 200: + '200': description: Success - schema: - $ref: '#/definitions/public_conversation' - 401: + content: + application/json: + schema: + $ref: '#/components/schemas/public_conversation' + '401': description: Unauthorized - 404: + content: + application/json: + schema: + $ref: '#/components/schemas/bad_request_error' + '404': description: Conversation not found + content: + application/json: + schema: + $ref: '#/components/schemas/bad_request_error' diff --git a/swagger/paths/public/inboxes/conversations/toggle_status.yml b/swagger/paths/public/inboxes/conversations/toggle_status.yml index cac1c3d8d..bfbd41c70 100644 --- a/swagger/paths/public/inboxes/conversations/toggle_status.yml +++ b/swagger/paths/public/inboxes/conversations/toggle_status.yml @@ -3,12 +3,23 @@ tags: operationId: resolve-conversation summary: Resolve a conversation description: Marks a conversation as resolved +security: [] responses: - 200: + '200': description: Conversation resolved successfully - schema: - $ref: '#/definitions/public_conversation' - 401: + content: + application/json: + schema: + $ref: '#/components/schemas/public_conversation' + '401': description: Unauthorized - 404: + content: + application/json: + schema: + $ref: '#/components/schemas/bad_request_error' + '404': description: Conversation not found + content: + application/json: + schema: + $ref: '#/components/schemas/bad_request_error' diff --git a/swagger/paths/public/inboxes/conversations/toggle_typing.yml b/swagger/paths/public/inboxes/conversations/toggle_typing.yml index af01c77f6..603ed0f8d 100644 --- a/swagger/paths/public/inboxes/conversations/toggle_typing.yml +++ b/swagger/paths/public/inboxes/conversations/toggle_typing.yml @@ -3,16 +3,38 @@ tags: operationId: toggle-typing-status summary: Toggle typing status description: Toggles the typing status in a conversation +security: [] parameters: - name: typing_status in: query required: true - type: string + schema: + type: string description: Typing status, either 'on' or 'off' +requestBody: + required: true + content: + application/json: + schema: + type: object + properties: + typing_status: + type: string + enum: ['on', 'off'] + description: The typing status to set + example: 'on' responses: - 200: + '200': description: Typing status toggled successfully - 401: + '401': description: Unauthorized - 404: + content: + application/json: + schema: + $ref: '#/components/schemas/bad_request_error' + '404': description: Conversation not found + content: + application/json: + schema: + $ref: '#/components/schemas/bad_request_error' diff --git a/swagger/paths/public/inboxes/conversations/update_last_seen.yml b/swagger/paths/public/inboxes/conversations/update_last_seen.yml index 4e56aa11d..a3e199e73 100644 --- a/swagger/paths/public/inboxes/conversations/update_last_seen.yml +++ b/swagger/paths/public/inboxes/conversations/update_last_seen.yml @@ -3,10 +3,19 @@ tags: operationId: update-last-seen summary: Update last seen description: Updates the last seen time of the contact in a conversation +security: [] responses: - 200: + '200': description: Last seen updated successfully - 401: + '401': description: Unauthorized - 404: + content: + application/json: + schema: + $ref: '#/components/schemas/bad_request_error' + '404': description: Conversation not found + content: + application/json: + schema: + $ref: '#/components/schemas/bad_request_error' diff --git a/swagger/paths/public/inboxes/messages/create.yml b/swagger/paths/public/inboxes/messages/create.yml index 902090952..1afa7d731 100644 --- a/swagger/paths/public/inboxes/messages/create.yml +++ b/swagger/paths/public/inboxes/messages/create.yml @@ -4,16 +4,22 @@ operationId: create-a-message summary: Create a message description: Create a message security: [] -parameters: - - name: data - in: body - required: true - schema: - $ref: '#/definitions/public_message_create_payload' +requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/public_message_create_payload' responses: - 200: + '200': description: Success - schema: - $ref: '#/definitions/public_message' - 401: + content: + application/json: + schema: + $ref: '#/components/schemas/public_message' + '401': description: Unauthorized + content: + application/json: + schema: + $ref: '#/components/schemas/bad_request_error' diff --git a/swagger/paths/public/inboxes/messages/index.yml b/swagger/paths/public/inboxes/messages/index.yml index 0c9eaeab3..68e34fed7 100644 --- a/swagger/paths/public/inboxes/messages/index.yml +++ b/swagger/paths/public/inboxes/messages/index.yml @@ -3,13 +3,20 @@ tags: operationId: list-all-converation-messages summary: List all messages description: List all messages in the conversation +security: [] responses: - 200: + '200': description: Success - schema: - type: array - description: 'Array of messages' - items: - $ref: '#/definitions/public_message' - 401: + content: + application/json: + schema: + type: array + description: 'Array of messages' + items: + $ref: '#/components/schemas/public_message' + '401': description: Unauthorized + content: + application/json: + schema: + $ref: '#/components/schemas/bad_request_error' diff --git a/swagger/paths/public/inboxes/messages/update.yml b/swagger/paths/public/inboxes/messages/update.yml index a211f39ca..38e41beed 100644 --- a/swagger/paths/public/inboxes/messages/update.yml +++ b/swagger/paths/public/inboxes/messages/update.yml @@ -4,16 +4,22 @@ operationId: update-a-message summary: Update a message description: Update a message security: [] -parameters: - - name: data - in: body - required: true - schema: - $ref: '#/definitions/public_message_update_payload' +requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/public_message_update_payload' responses: - 200: + '200': description: Success - schema: - $ref: '#/definitions/public_message' - 401: + content: + application/json: + schema: + $ref: '#/components/schemas/public_message' + '401': description: Unauthorized + content: + application/json: + schema: + $ref: '#/components/schemas/bad_request_error' diff --git a/swagger/paths/public/inboxes/show.yml b/swagger/paths/public/inboxes/show.yml index 094caabbf..1effd078b 100644 --- a/swagger/paths/public/inboxes/show.yml +++ b/swagger/paths/public/inboxes/show.yml @@ -5,11 +5,21 @@ summary: Inbox details description: Get the details of an inbox security: [] responses: - 200: + '200': description: Success - schema: - $ref: '#/definitions/public_inbox' - 401: + content: + application/json: + schema: + $ref: '#/components/schemas/public_inbox' + '401': description: Unauthorized - 404: + content: + application/json: + schema: + $ref: '#/components/schemas/bad_request_error' + '404': description: The given inbox does not exist + content: + application/json: + schema: + $ref: '#/components/schemas/bad_request_error' diff --git a/swagger/paths/survey/show.yml b/swagger/paths/survey/show.yml index c2bd1e8e8..ed9c71785 100644 --- a/swagger/paths/survey/show.yml +++ b/swagger/paths/survey/show.yml @@ -5,5 +5,5 @@ summary: Get CSAT survey page description: You can redirect the client to this URL, instead of implementing the CSAT survey component yourself. security: [] responses: - 200: + '200': description: Success diff --git a/swagger/swagger.json b/swagger/swagger.json index 19ef5c904..943b7a3e8 100644 --- a/swagger/swagger.json +++ b/swagger/swagger.json @@ -1,9 +1,9 @@ { - "swagger": "2.0", + "openapi": "3.0.4", "info": { - "description": "This is the API documentation for Chatwoot server.", - "version": "1.0.0", "title": "Chatwoot", + "description": "This is the API documentation for Chatwoot server.", + "version": "1.1.0", "termsOfService": "https://www.chatwoot.com/terms-of-service/", "contact": { "email": "hello@chatwoot.com" @@ -13,42 +13,9 @@ "url": "https://opensource.org/licenses/MIT" } }, - "host": "app.chatwoot.com", - "basePath": "/", - "schemes": [ - "https" - ], - "produces": [ - "application/json; charset=utf-8" - ], - "consumes": [ - "application/json; charset=utf-8" - ], - "securityDefinitions": { - "userApiKey": { - "type": "apiKey", - "in": "header", - "name": "api_access_token", - "description": "This token can be obtained by visiting the profile page or via rails console. Provides access to endpoints based on the user permissions levels. This token can be saved by an external system when user is created via API, to perform activities on behalf of the user." - }, - "agentBotApiKey": { - "type": "apiKey", - "in": "header", - "name": "api_access_token", - "description": "This token should be provided by system admin or obtained via rails console. This token can be used to build bot integrations and can only access limited apis." - }, - "platformAppApiKey": { - "type": "apiKey", - "in": "header", - "name": "api_access_token", - "description": "This token can be obtained by the system admin after creating a platformApp. This token should be used to provision agent bots, accounts, users and their roles." - } - }, - "security": [ + "servers": [ { - "userApiKey": [ - - ] + "url": "https://app.chatwoot.com/" } ], "paths": { @@ -62,30 +29,39 @@ "description": "Create an Account", "security": [ { - "platformAppApiKey": [ - - ] + "platformAppApiKey": [] } ], - "parameters": [ - { - "name": "data", - "in": "body", - "required": true, - "schema": { - "$ref": "#/definitions/account_create_update_payload" + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/account_create_update_payload" + } } } - ], + }, "responses": { "200": { "description": "Success", - "schema": { - "$ref": "#/definitions/platform_account" + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/platform_account" + } + } } }, "401": { - "description": "Unauthorized" + "description": "Unauthorized", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/bad_request_error" + } + } + } } } } @@ -93,7 +69,7 @@ "/platform/api/v1/accounts/{account_id}": { "parameters": [ { - "$ref": "#/parameters/account_id" + "$ref": "#/components/parameters/account_id" } ], "get": { @@ -105,23 +81,39 @@ "description": "Get the details of an account", "security": [ { - "platformAppApiKey": [ - - ] + "platformAppApiKey": [] } ], "responses": { "200": { "description": "Success", - "schema": { - "$ref": "#/definitions/platform_account" + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/platform_account" + } + } } }, "401": { - "description": "Unauthorized" + "description": "Unauthorized", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/bad_request_error" + } + } + } }, "404": { - "description": "The given account does not exist" + "description": "The given account does not exist", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/bad_request_error" + } + } + } } } }, @@ -134,30 +126,39 @@ "description": "Update an account's attributes", "security": [ { - "platformAppApiKey": [ - - ] + "platformAppApiKey": [] } ], - "parameters": [ - { - "name": "data", - "in": "body", - "required": true, - "schema": { - "$ref": "#/definitions/account_create_update_payload" + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/account_create_update_payload" + } } } - ], + }, "responses": { "200": { "description": "Success", - "schema": { - "$ref": "#/definitions/platform_account" + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/platform_account" + } + } } }, "401": { - "description": "Unauthorized" + "description": "Unauthorized", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/bad_request_error" + } + } + } } } }, @@ -170,9 +171,7 @@ "description": "Delete an Account", "security": [ { - "platformAppApiKey": [ - - ] + "platformAppApiKey": [] } ], "responses": { @@ -180,10 +179,24 @@ "description": "Success" }, "401": { - "description": "Unauthorized" + "description": "Unauthorized", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/bad_request_error" + } + } + } }, "404": { - "description": "The account does not exist" + "description": "The account does not exist", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/bad_request_error" + } + } + } } } } @@ -191,7 +204,7 @@ "/platform/api/v1/accounts/{account_id}/account_users": { "parameters": [ { - "$ref": "#/parameters/account_id" + "$ref": "#/components/parameters/account_id" } ], "get": { @@ -203,38 +216,29 @@ "description": "List all account users", "security": [ { - "platformAppApiKey": [ - - ] + "platformAppApiKey": [] } ], "responses": { "200": { "description": "Success", - "schema": { - "type": "array", - "description": "Array of account users", - "items": { - "type": "object", - "properties": { - "account_id": { - "type": "integer", - "description": "The ID of the user" - }, - "user_id": { - "type": "integer", - "description": "The ID of the user" - }, - "role": { - "type": "string", - "description": "whether user is an administrator or agent" - } + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/account_user" } } } }, "401": { - "description": "Unauthorized" + "description": "Unauthorized", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/bad_request_error" + } + } + } } } }, @@ -247,57 +251,53 @@ "description": "Create an Account User", "security": [ { - "platformAppApiKey": [ - - ] + "platformAppApiKey": [] } ], - "parameters": [ - { - "name": "data", - "in": "body", - "required": true, - "schema": { - "type": "object", - "required": [ - "user_id", - "role" - ], - "properties": { - "user_id": { - "type": "integer", - "description": "The ID of the user" - }, - "role": { - "type": "string", - "description": "whether user is an administrator or agent" - } + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/account_user_create_update_payload" } } } - ], + }, "responses": { "200": { "description": "Success", - "schema": { - "properties": { - "account_id": { - "type": "integer", - "description": "The ID of the user" - }, - "user_id": { - "type": "integer", - "description": "The ID of the user" - }, - "role": { - "type": "string", - "description": "whether user is an administrator or agent" + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "account_id": { + "type": "integer", + "description": "The ID of the account" + }, + "user_id": { + "type": "integer", + "description": "The ID of the user" + }, + "role": { + "type": "string", + "description": "whether user is an administrator or agent" + } + } } } } }, "401": { - "description": "Unauthorized" + "description": "Unauthorized", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/bad_request_error" + } + } + } } } }, @@ -310,28 +310,7 @@ "description": "Delete an Account User", "security": [ { - "platformAppApiKey": [ - - ] - } - ], - "parameters": [ - { - "name": "data", - "in": "body", - "required": true, - "schema": { - "type": "object", - "required": [ - "user_id" - ], - "properties": { - "user_id": { - "type": "integer", - "description": "The ID of the user" - } - } - } + "platformAppApiKey": [] } ], "responses": { @@ -339,10 +318,24 @@ "description": "Success" }, "401": { - "description": "Unauthorized" + "description": "Unauthorized", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/bad_request_error" + } + } + } }, "404": { - "description": "The account does not exist" + "description": "The account does not exist", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/bad_request_error" + } + } + } } } } @@ -357,24 +350,33 @@ "description": "List all agent bots available", "security": [ { - "platformAppApiKey": [ - - ] + "platformAppApiKey": [] } ], "responses": { "200": { "description": "Success", - "schema": { - "type": "array", - "description": "Array of agent bots", - "items": { - "$ref": "#/definitions/agent_bot" + "content": { + "application/json": { + "schema": { + "type": "array", + "description": "Array of agent bots", + "items": { + "$ref": "#/components/schemas/agent_bot" + } + } } } }, "401": { - "description": "Unauthorized" + "description": "Unauthorized", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/bad_request_error" + } + } + } } } }, @@ -387,30 +389,39 @@ "description": "Create an agent bot", "security": [ { - "platformAppApiKey": [ - - ] + "platformAppApiKey": [] } ], - "parameters": [ - { - "name": "data", - "in": "body", - "required": true, - "schema": { - "$ref": "#/definitions/agent_bot_create_update_payload" + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/platform_agent_bot_create_update_payload" + } } } - ], + }, "responses": { "200": { "description": "Success", - "schema": { - "$ref": "#/definitions/agent_bot" + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/agent_bot" + } + } } }, "401": { - "description": "Unauthorized" + "description": "Unauthorized", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/bad_request_error" + } + } + } } } } @@ -418,7 +429,7 @@ "/platform/api/v1/agent_bots/{id}": { "parameters": [ { - "$ref": "#/parameters/agent_bot_id" + "$ref": "#/components/parameters/agent_bot_id" } ], "get": { @@ -430,23 +441,39 @@ "description": "Get the details of an agent bot", "security": [ { - "platformAppApiKey": [ - - ] + "platformAppApiKey": [] } ], "responses": { "200": { "description": "Success", - "schema": { - "$ref": "#/definitions/agent_bot" + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/agent_bot" + } + } } }, "401": { - "description": "Unauthorized" + "description": "Unauthorized", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/bad_request_error" + } + } + } }, "404": { - "description": "The given agent bot ID does not exist" + "description": "The given agent bot ID does not exist", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/bad_request_error" + } + } + } } } }, @@ -459,30 +486,39 @@ "description": "Update an agent bot's attributes", "security": [ { - "platformAppApiKey": [ - - ] + "platformAppApiKey": [] } ], - "parameters": [ - { - "name": "data", - "in": "body", - "required": true, - "schema": { - "$ref": "#/definitions/agent_bot_create_update_payload" + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/platform_agent_bot_create_update_payload" + } } } - ], + }, "responses": { "200": { "description": "Success", - "schema": { - "$ref": "#/definitions/agent_bot" + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/agent_bot" + } + } } }, "401": { - "description": "Unauthorized" + "description": "Unauthorized", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/bad_request_error" + } + } + } } } }, @@ -495,9 +531,7 @@ "description": "Delete an AgentBot", "security": [ { - "platformAppApiKey": [ - - ] + "platformAppApiKey": [] } ], "responses": { @@ -505,10 +539,24 @@ "description": "Success" }, "401": { - "description": "Unauthorized" + "description": "Unauthorized", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/bad_request_error" + } + } + } }, "404": { - "description": "The agent bot does not exist" + "description": "The agent bot does not exist", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/bad_request_error" + } + } + } } } } @@ -523,30 +571,39 @@ "description": "Create a User", "security": [ { - "platformAppApiKey": [ - - ] + "platformAppApiKey": [] } ], - "parameters": [ - { - "name": "data", - "in": "body", - "required": true, - "schema": { - "$ref": "#/definitions/user_create_update_payload" + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/user_create_update_payload" + } } } - ], + }, "responses": { "200": { "description": "Success", - "schema": { - "$ref": "#/definitions/user" + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/user" + } + } } }, "401": { - "description": "Unauthorized" + "description": "Unauthorized", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/bad_request_error" + } + } + } } } } @@ -554,7 +611,7 @@ "/platform/api/v1/users/{id}": { "parameters": [ { - "$ref": "#/parameters/platform_user_id" + "$ref": "#/components/parameters/platform_user_id" } ], "get": { @@ -566,23 +623,39 @@ "description": "Get the details of an user", "security": [ { - "platformAppApiKey": [ - - ] + "platformAppApiKey": [] } ], "responses": { "200": { "description": "Success", - "schema": { - "$ref": "#/definitions/user" + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/user" + } + } } }, "401": { - "description": "Unauthorized" + "description": "Unauthorized", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/bad_request_error" + } + } + } }, "404": { - "description": "The given user does not exist" + "description": "The given user does not exist", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/bad_request_error" + } + } + } } } }, @@ -595,30 +668,39 @@ "description": "Update a user's attributes", "security": [ { - "platformAppApiKey": [ - - ] + "platformAppApiKey": [] } ], - "parameters": [ - { - "name": "data", - "in": "body", - "required": true, - "schema": { - "$ref": "#/definitions/user_create_update_payload" + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/user_create_update_payload" + } } } - ], + }, "responses": { "200": { "description": "Success", - "schema": { - "$ref": "#/definitions/user" + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/user" + } + } } }, "401": { - "description": "Unauthorized" + "description": "Unauthorized", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/bad_request_error" + } + } + } } } }, @@ -631,9 +713,7 @@ "description": "Delete a User", "security": [ { - "platformAppApiKey": [ - - ] + "platformAppApiKey": [] } ], "responses": { @@ -641,10 +721,24 @@ "description": "Success" }, "401": { - "description": "Unauthorized" + "description": "Unauthorized", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/bad_request_error" + } + } + } }, "404": { - "description": "The user does not exist" + "description": "The user does not exist", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/bad_request_error" + } + } + } } } } @@ -652,7 +746,7 @@ "/platform/api/v1/users/{id}/login": { "parameters": [ { - "$ref": "#/parameters/platform_user_id" + "$ref": "#/components/parameters/platform_user_id" } ], "get": { @@ -664,29 +758,45 @@ "description": "Get the sso link of a user", "security": [ { - "platformAppApiKey": [ - - ] + "platformAppApiKey": [] } ], "responses": { "200": { "description": "Success", - "schema": { - "type": "object", - "properties": { - "url": { - "type": "string", - "description": "SSO url to autenticate the user" + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "url": { + "type": "string", + "description": "SSO url to autenticate the user" + } + } } } } }, "401": { - "description": "Unauthorized" + "description": "Unauthorized", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/bad_request_error" + } + } + } }, "404": { - "description": "The given user does not exist" + "description": "The given user does not exist", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/bad_request_error" + } + } + } } } } @@ -694,7 +804,7 @@ "/public/api/v1/inboxes/{inbox_identifier}": { "parameters": [ { - "$ref": "#/parameters/public_inbox_identifier" + "$ref": "#/components/parameters/public_inbox_identifier" } ], "get": { @@ -704,21 +814,37 @@ "operationId": "get-details-of-a-inbox", "summary": "Inbox details", "description": "Get the details of an inbox", - "security": [ - - ], + "security": [], "responses": { "200": { "description": "Success", - "schema": { - "$ref": "#/definitions/public_inbox" + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/public_inbox" + } + } } }, "401": { - "description": "Unauthorized" + "description": "Unauthorized", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/bad_request_error" + } + } + } }, "404": { - "description": "The given inbox does not exist" + "description": "The given inbox does not exist", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/bad_request_error" + } + } + } } } } @@ -726,7 +852,7 @@ "/public/api/v1/inboxes/{inbox_identifier}/contacts": { "parameters": [ { - "$ref": "#/parameters/public_inbox_identifier" + "$ref": "#/components/parameters/public_inbox_identifier" } ], "post": { @@ -736,28 +862,37 @@ "operationId": "create-a-contact", "summary": "Create a contact", "description": "Create a contact", - "security": [ - - ], - "parameters": [ - { - "name": "data", - "in": "body", - "required": true, - "schema": { - "$ref": "#/definitions/public_contact_create_update_payload" + "security": [], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/public_contact_create_update_payload" + } } } - ], + }, "responses": { "200": { "description": "Success", - "schema": { - "$ref": "#/definitions/public_contact" + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/public_contact" + } + } } }, "401": { - "description": "Unauthorized" + "description": "Unauthorized", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/bad_request_error" + } + } + } } } } @@ -765,10 +900,10 @@ "/public/api/v1/inboxes/{inbox_identifier}/contacts/{contact_identifier}": { "parameters": [ { - "$ref": "#/parameters/public_inbox_identifier" + "$ref": "#/components/parameters/public_inbox_identifier" }, { - "$ref": "#/parameters/public_contact_identifier" + "$ref": "#/components/parameters/public_contact_identifier" } ], "get": { @@ -778,21 +913,37 @@ "operationId": "get-details-of-a-contact", "summary": "Get a contact", "description": "Get the details of a contact", - "security": [ - - ], + "security": [], "responses": { "200": { "description": "Success", - "schema": { - "$ref": "#/definitions/public_contact" + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/public_contact" + } + } } }, "401": { - "description": "Unauthorized" + "description": "Unauthorized", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/bad_request_error" + } + } + } }, "404": { - "description": "The given contact does not exist" + "description": "The given contact does not exist", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/bad_request_error" + } + } + } } } }, @@ -803,28 +954,37 @@ "operationId": "update-a-contact", "summary": "Update a contact", "description": "Update a contact's attributes", - "security": [ - - ], - "parameters": [ - { - "name": "data", - "in": "body", - "required": true, - "schema": { - "$ref": "#/definitions/public_contact_create_update_payload" + "security": [], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/public_contact_create_update_payload" + } } } - ], + }, "responses": { "200": { "description": "Success", - "schema": { - "$ref": "#/definitions/public_contact" + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/public_contact" + } + } } }, "401": { - "description": "Unauthorized" + "description": "Unauthorized", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/bad_request_error" + } + } + } } } } @@ -832,10 +992,10 @@ "/public/api/v1/inboxes/{inbox_identifier}/contacts/{contact_identifier}/conversations": { "parameters": [ { - "$ref": "#/parameters/public_inbox_identifier" + "$ref": "#/components/parameters/public_inbox_identifier" }, { - "$ref": "#/parameters/public_contact_identifier" + "$ref": "#/components/parameters/public_contact_identifier" } ], "post": { @@ -845,28 +1005,37 @@ "operationId": "create-a-conversation", "summary": "Create a conversation", "description": "Create a conversation", - "security": [ - - ], - "parameters": [ - { - "name": "data", - "in": "body", - "required": true, - "schema": { - "$ref": "#/definitions/public_conversation_create_payload" + "security": [], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/public_conversation_create_payload" + } } } - ], + }, "responses": { "200": { "description": "Success", - "schema": { - "$ref": "#/definitions/public_conversation" + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/public_conversation" + } + } } }, "401": { - "description": "Unauthorized" + "description": "Unauthorized", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/bad_request_error" + } + } + } } } }, @@ -877,19 +1046,31 @@ "operationId": "list-all-contact-conversations", "summary": "List all conversations", "description": "List all conversations for the contact", + "security": [], "responses": { "200": { "description": "Success", - "schema": { - "type": "array", - "description": "Array of conversations", - "items": { - "$ref": "#/definitions/public_conversation" + "content": { + "application/json": { + "schema": { + "type": "array", + "description": "Array of conversations", + "items": { + "$ref": "#/components/schemas/public_conversation" + } + } } } }, "401": { - "description": "Unauthorized" + "description": "Unauthorized", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/bad_request_error" + } + } + } } } } @@ -897,13 +1078,13 @@ "/public/api/v1/inboxes/{inbox_identifier}/contacts/{contact_identifier}/conversations/{conversation_id}": { "parameters": [ { - "$ref": "#/parameters/public_inbox_identifier" + "$ref": "#/components/parameters/public_inbox_identifier" }, { - "$ref": "#/parameters/public_contact_identifier" + "$ref": "#/components/parameters/public_contact_identifier" }, { - "$ref": "#/parameters/conversation_id" + "$ref": "#/components/parameters/conversation_id" } ], "get": { @@ -913,18 +1094,37 @@ "operationId": "get-single-conversation", "summary": "Get a single conversation", "description": "Retrieves the details of a specific conversation", + "security": [], "responses": { "200": { "description": "Success", - "schema": { - "$ref": "#/definitions/public_conversation" + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/public_conversation" + } + } } }, "401": { - "description": "Unauthorized" + "description": "Unauthorized", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/bad_request_error" + } + } + } }, "404": { - "description": "Conversation not found" + "description": "Conversation not found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/bad_request_error" + } + } + } } } } @@ -932,13 +1132,13 @@ "/public/api/v1/inboxes/{inbox_identifier}/contacts/{contact_identifier}/conversations/{conversation_id}/toggle_status": { "parameters": [ { - "$ref": "#/parameters/public_inbox_identifier" + "$ref": "#/components/parameters/public_inbox_identifier" }, { - "$ref": "#/parameters/public_contact_identifier" + "$ref": "#/components/parameters/public_contact_identifier" }, { - "$ref": "#/parameters/conversation_id" + "$ref": "#/components/parameters/conversation_id" } ], "post": { @@ -948,18 +1148,37 @@ "operationId": "resolve-conversation", "summary": "Resolve a conversation", "description": "Marks a conversation as resolved", + "security": [], "responses": { "200": { "description": "Conversation resolved successfully", - "schema": { - "$ref": "#/definitions/public_conversation" + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/public_conversation" + } + } } }, "401": { - "description": "Unauthorized" + "description": "Unauthorized", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/bad_request_error" + } + } + } }, "404": { - "description": "Conversation not found" + "description": "Conversation not found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/bad_request_error" + } + } + } } } } @@ -967,13 +1186,13 @@ "/public/api/v1/inboxes/{inbox_identifier}/contacts/{contact_identifier}/conversations/{conversation_id}/toggle_typing": { "parameters": [ { - "$ref": "#/parameters/public_inbox_identifier" + "$ref": "#/components/parameters/public_inbox_identifier" }, { - "$ref": "#/parameters/public_contact_identifier" + "$ref": "#/components/parameters/public_contact_identifier" }, { - "$ref": "#/parameters/conversation_id" + "$ref": "#/components/parameters/conversation_id" } ], "post": { @@ -983,24 +1202,62 @@ "operationId": "toggle-typing-status", "summary": "Toggle typing status", "description": "Toggles the typing status in a conversation", + "security": [], "parameters": [ { "name": "typing_status", "in": "query", "required": true, - "type": "string", + "schema": { + "type": "string" + }, "description": "Typing status, either 'on' or 'off'" } ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "typing_status": { + "type": "string", + "enum": [ + "on", + "off" + ], + "description": "The typing status to set", + "example": "on" + } + } + } + } + } + }, "responses": { "200": { "description": "Typing status toggled successfully" }, "401": { - "description": "Unauthorized" + "description": "Unauthorized", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/bad_request_error" + } + } + } }, "404": { - "description": "Conversation not found" + "description": "Conversation not found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/bad_request_error" + } + } + } } } } @@ -1008,13 +1265,13 @@ "/public/api/v1/inboxes/{inbox_identifier}/contacts/{contact_identifier}/conversations/{conversation_id}/update_last_seen": { "parameters": [ { - "$ref": "#/parameters/public_inbox_identifier" + "$ref": "#/components/parameters/public_inbox_identifier" }, { - "$ref": "#/parameters/public_contact_identifier" + "$ref": "#/components/parameters/public_contact_identifier" }, { - "$ref": "#/parameters/conversation_id" + "$ref": "#/components/parameters/conversation_id" } ], "post": { @@ -1024,15 +1281,30 @@ "operationId": "update-last-seen", "summary": "Update last seen", "description": "Updates the last seen time of the contact in a conversation", + "security": [], "responses": { "200": { "description": "Last seen updated successfully" }, "401": { - "description": "Unauthorized" + "description": "Unauthorized", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/bad_request_error" + } + } + } }, "404": { - "description": "Conversation not found" + "description": "Conversation not found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/bad_request_error" + } + } + } } } } @@ -1040,13 +1312,13 @@ "/public/api/v1/inboxes/{inbox_identifier}/contacts/{contact_identifier}/conversations/{conversation_id}/messages": { "parameters": [ { - "$ref": "#/parameters/public_inbox_identifier" + "$ref": "#/components/parameters/public_inbox_identifier" }, { - "$ref": "#/parameters/public_contact_identifier" + "$ref": "#/components/parameters/public_contact_identifier" }, { - "$ref": "#/parameters/conversation_id" + "$ref": "#/components/parameters/conversation_id" } ], "post": { @@ -1056,28 +1328,37 @@ "operationId": "create-a-message", "summary": "Create a message", "description": "Create a message", - "security": [ - - ], - "parameters": [ - { - "name": "data", - "in": "body", - "required": true, - "schema": { - "$ref": "#/definitions/public_message_create_payload" + "security": [], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/public_message_create_payload" + } } } - ], + }, "responses": { "200": { "description": "Success", - "schema": { - "$ref": "#/definitions/public_message" + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/public_message" + } + } } }, "401": { - "description": "Unauthorized" + "description": "Unauthorized", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/bad_request_error" + } + } + } } } }, @@ -1088,19 +1369,31 @@ "operationId": "list-all-converation-messages", "summary": "List all messages", "description": "List all messages in the conversation", + "security": [], "responses": { "200": { "description": "Success", - "schema": { - "type": "array", - "description": "Array of messages", - "items": { - "$ref": "#/definitions/public_message" + "content": { + "application/json": { + "schema": { + "type": "array", + "description": "Array of messages", + "items": { + "$ref": "#/components/schemas/public_message" + } + } } } }, "401": { - "description": "Unauthorized" + "description": "Unauthorized", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/bad_request_error" + } + } + } } } } @@ -1108,16 +1401,16 @@ "/public/api/v1/inboxes/{inbox_identifier}/contacts/{contact_identifier}/conversations/{conversation_id}/messages/{message_id}": { "parameters": [ { - "$ref": "#/parameters/public_inbox_identifier" + "$ref": "#/components/parameters/public_inbox_identifier" }, { - "$ref": "#/parameters/public_contact_identifier" + "$ref": "#/components/parameters/public_contact_identifier" }, { - "$ref": "#/parameters/conversation_id" + "$ref": "#/components/parameters/conversation_id" }, { - "$ref": "#/parameters/message_id" + "$ref": "#/components/parameters/message_id" } ], "patch": { @@ -1127,101 +1420,37 @@ "operationId": "update-a-message", "summary": "Update a message", "description": "Update a message", - "security": [ - - ], - "parameters": [ - { - "name": "data", - "in": "body", - "required": true, - "schema": { - "$ref": "#/definitions/public_message_update_payload" - } - } - ], - "responses": { - "200": { - "description": "Success", - "schema": { - "$ref": "#/definitions/public_message" - } - }, - "401": { - "description": "Unauthorized" - } - } - } - }, - "/api/v1/accounts/{account_id}/contacts/{contact_identifier}/labels": { - "parameters": [ - { - "$ref": "#/parameters/account_id" - }, - { - "$ref": "#/parameters/public_contact_identifier" - } - ], - "get": { - "tags": [ - "Contact Labels" - ], - "operationId": "list-all-labels-of-a-contact", - "summary": "List Labels", - "description": "Lists all the labels of a contact", - "responses": { - "200": { - "description": "Success", - "schema": { - "$ref": "#/definitions/contact_labels" - } - }, - "404": { - "description": "Contact not found" - }, - "401": { - "description": "Unauthorized" - } - } - }, - "post": { - "tags": [ - "Contact Labels" - ], - "operationId": "contact-add-labels", - "summary": "Add Labels", - "description": "Add labels to a contact. Note that this API would overwrite the existing list of labels associated to the conversation.", - "parameters": [ - { - "name": "data", - "in": "body", - "required": true, - "schema": { - "type": "object", - "properties": { - "labels": { - "type": "array", - "description": "Array of labels (comma-separated strings)", - "items": { - "type": "string" - } - } + "security": [], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/public_message_update_payload" } } } - ], + }, "responses": { "200": { "description": "Success", - "schema": { - "$ref": "#/definitions/contact_labels" + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/public_message" + } + } } }, - "404": { - "description": "Contact not found" - }, "401": { - "description": "Unauthorized" + "description": "Unauthorized", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/bad_request_error" + } + } + } } } } @@ -1229,7 +1458,7 @@ "/survey/responses/{conversation_uuid}": { "parameters": [ { - "$ref": "#/parameters/conversation_uuid" + "$ref": "#/components/parameters/conversation_uuid" } ], "get": { @@ -1239,9 +1468,7 @@ "operationId": "get-csat-survey-page", "summary": "Get CSAT survey page", "description": "You can redirect the client to this URL, instead of implementing the CSAT survey component yourself.", - "security": [ - - ], + "security": [], "responses": { "200": { "description": "Success" @@ -1252,7 +1479,7 @@ "/api/v1/accounts/{account_id}/agent_bots": { "parameters": [ { - "$ref": "#/parameters/account_id" + "$ref": "#/components/parameters/account_id" } ], "get": { @@ -1262,19 +1489,35 @@ "operationId": "list-all-account-agent-bots", "summary": "List all AgentBots", "description": "List all agent bots available for the current account", + "security": [ + { + "userApiKey": [] + } + ], "responses": { "200": { "description": "Success", - "schema": { - "type": "array", - "description": "Array of agent bots", - "items": { - "$ref": "#/definitions/agent_bot" + "content": { + "application/json": { + "schema": { + "type": "array", + "description": "Array of agent bots", + "items": { + "$ref": "#/components/schemas/agent_bot" + } + } } } }, "401": { - "description": "Unauthorized" + "description": "Unauthorized", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/bad_request_error" + } + } + } } } }, @@ -1285,28 +1528,41 @@ "operationId": "create-an-account-agent-bot", "summary": "Create an Agent Bot", "description": "Create an agent bot in the account", - "parameters": [ + "security": [ { - "$ref": "#/parameters/account_id" - }, - { - "name": "data", - "in": "body", - "required": true, - "schema": { - "$ref": "#/definitions/agent_bot_create_update_payload" - } + "userApiKey": [] } ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/agent_bot_create_update_payload" + } + } + } + }, "responses": { "200": { "description": "Success", - "schema": { - "$ref": "#/definitions/agent_bot" + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/agent_bot" + } + } } }, "401": { - "description": "Unauthorized" + "description": "Unauthorized", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/bad_request_error" + } + } + } } } } @@ -1314,10 +1570,10 @@ "/api/v1/accounts/{account_id}/agent_bots/{id}": { "parameters": [ { - "$ref": "#/parameters/account_id" + "$ref": "#/components/parameters/account_id" }, { - "$ref": "#/parameters/agent_bot_id" + "$ref": "#/components/parameters/agent_bot_id" } ], "get": { @@ -1327,18 +1583,41 @@ "operationId": "get-details-of-a-single-account-agent-bot", "summary": "Get an agent bot details", "description": "Get the details of an agent bot in the account", + "security": [ + { + "userApiKey": [] + } + ], "responses": { "200": { "description": "Success", - "schema": { - "$ref": "#/definitions/agent_bot" + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/agent_bot" + } + } } }, "401": { - "description": "Unauthorized" + "description": "Unauthorized", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/bad_request_error" + } + } + } }, "404": { - "description": "The given agent bot ID does not exist in the account" + "description": "The given agent bot ID does not exist in the account", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/bad_request_error" + } + } + } } } }, @@ -1349,25 +1628,41 @@ "operationId": "update-an-account-agent-bot", "summary": "Update an agent bot", "description": "Update an agent bot's attributes", - "parameters": [ + "security": [ { - "name": "data", - "in": "body", - "required": true, - "schema": { - "$ref": "#/definitions/agent_bot_create_update_payload" - } + "userApiKey": [] } ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/agent_bot_create_update_payload" + } + } + } + }, "responses": { "200": { "description": "Success", - "schema": { - "$ref": "#/definitions/agent_bot" + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/agent_bot" + } + } } }, "401": { - "description": "Unauthorized" + "description": "Unauthorized", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/bad_request_error" + } + } + } } } }, @@ -1378,15 +1673,34 @@ "operationId": "delete-an-account-agent-bot", "summary": "Delete an AgentBot", "description": "Delete an AgentBot from the account", + "security": [ + { + "userApiKey": [] + } + ], "responses": { "200": { "description": "Success" }, "401": { - "description": "Unauthorized" + "description": "Unauthorized", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/bad_request_error" + } + } + } }, "404": { - "description": "The agent bot does not exist in the account" + "description": "The agent bot does not exist in the account", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/bad_request_error" + } + } + } } } } @@ -1394,7 +1708,7 @@ "/api/v1/accounts/{account_id}/agents": { "parameters": [ { - "$ref": "#/parameters/account_id" + "$ref": "#/components/parameters/account_id" } ], "get": { @@ -1406,24 +1720,33 @@ "description": "Get Details of Agents in an Account", "security": [ { - "userApiKey": [ - - ] + "userApiKey": [] } ], "responses": { "200": { "description": "Success", - "schema": { - "type": "array", - "description": "Array of all active agents", - "items": { - "$ref": "#/definitions/agent" + "content": { + "application/json": { + "schema": { + "type": "array", + "description": "Array of all active agents", + "items": { + "$ref": "#/components/schemas/agent" + } + } } } }, "403": { - "description": "Access denied" + "description": "Access denied", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/bad_request_error" + } + } + } } } }, @@ -1436,66 +1759,39 @@ "description": "Add a new Agent to Account", "security": [ { - "userApiKey": [ - - ] + "userApiKey": [] } ], - "parameters": [ - { - "name": "data", - "in": "body", - "required": true, - "schema": { - "type": "object", - "required": [ - "name", - "email", - "role" - ], - "properties": { - "name": { - "type": "string", - "description": "Full Name of the agent" - }, - "email": { - "type": "string", - "description": "Email of the Agent" - }, - "role": { - "type": "string", - "enum": [ - "agent", - "administrator" - ], - "description": "Whether its administrator or agent" - }, - "availability_status": { - "type": "string", - "enum": [ - "available", - "busy", - "offline" - ], - "description": "The availability setting of the agent." - }, - "auto_offline": { - "type": "boolean", - "description": "Whether the availability status of agent is configured to go offline automatically when away." - } + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/agent_create_payload" } } } - ], + }, "responses": { "200": { "description": "Success", - "schema": { - "$ref": "#/definitions/agent" + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/agent" + } + } } }, "403": { - "description": "Access denied" + "description": "Access denied", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/bad_request_error" + } + } + } } } } @@ -1503,7 +1799,7 @@ "/api/v1/accounts/{account_id}/agents/{id}": { "parameters": [ { - "$ref": "#/parameters/account_id" + "$ref": "#/components/parameters/account_id" } ], "patch": { @@ -1515,66 +1811,60 @@ "description": "Update an Agent in Account", "security": [ { - "userApiKey": [ - - ] + "userApiKey": [] } ], "parameters": [ { "in": "path", "name": "id", - "type": "integer", + "schema": { + "type": "integer" + }, "required": true, "description": "The ID of the agent to be updated." - }, - { - "name": "data", - "in": "body", - "required": true, - "schema": { - "type": "object", - "required": [ - "role" - ], - "properties": { - "role": { - "type": "string", - "enum": [ - "agent", - "administrator" - ], - "description": "Whether its administrator or agent" - }, - "availability": { - "type": "string", - "enum": [ - "available", - "busy", - "offline" - ], - "description": "The availability setting of the agent." - }, - "auto_offline": { - "type": "boolean", - "description": "Whether the availability status of agent is configured to go offline automatically when away." - } + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/agent_update_payload" } } } - ], + }, "responses": { "200": { "description": "Success", - "schema": { - "$ref": "#/definitions/agent" + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/agent" + } + } } }, "404": { - "description": "Agent not found" + "description": "Agent not found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/bad_request_error" + } + } + } }, "403": { - "description": "Access denied" + "description": "Access denied", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/bad_request_error" + } + } + } } } }, @@ -1587,18 +1877,18 @@ "description": "Remove an Agent from Account", "security": [ { - "userApiKey": [ - - ] + "userApiKey": [] } ], "parameters": [ { "in": "path", "name": "id", - "type": "integer", + "schema": { + "type": "integer" + }, "required": true, - "description": "The ID of the agent to be deleted" + "description": "The ID of the agent to be deleted." } ], "responses": { @@ -1606,10 +1896,24 @@ "description": "Success" }, "404": { - "description": "Agent not found" + "description": "Agent not found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/bad_request_error" + } + } + } }, "403": { - "description": "Access denied" + "description": "Access denied", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/bad_request_error" + } + } + } } } } @@ -1617,7 +1921,7 @@ "/api/v1/accounts/{account_id}/canned_responses": { "parameters": [ { - "$ref": "#/parameters/account_id" + "$ref": "#/components/parameters/account_id" } ], "get": { @@ -1629,24 +1933,33 @@ "description": "Get Details of Canned Responses in an Account", "security": [ { - "userApiKey": [ - - ] + "userApiKey": [] } ], "responses": { "200": { "description": "Success", - "schema": { - "type": "array", - "description": "Array of all canned responses", - "items": { - "$ref": "#/definitions/canned_response" + "content": { + "application/json": { + "schema": { + "type": "array", + "description": "Array of all canned responses", + "items": { + "$ref": "#/components/schemas/canned_response" + } + } } } }, "403": { - "description": "Access denied" + "description": "Access denied", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/bad_request_error" + } + } + } } } }, @@ -1659,30 +1972,39 @@ "description": "Add a new Canned Response to Account", "security": [ { - "userApiKey": [ - - ] + "userApiKey": [] } ], - "parameters": [ - { - "name": "data", - "in": "body", - "required": true, - "schema": { - "$ref": "#/definitions/canned_response_create_update_payload" + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/canned_response_create_update_payload" + } } } - ], + }, "responses": { "200": { "description": "Success", - "schema": { - "$ref": "#/definitions/canned_response" + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/canned_response" + } + } } }, "403": { - "description": "Access denied" + "description": "Access denied", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/bad_request_error" + } + } + } } } } @@ -1690,52 +2012,72 @@ "/api/v1/accounts/{account_id}/canned_responses/{id}": { "parameters": [ { - "$ref": "#/parameters/account_id" + "$ref": "#/components/parameters/account_id" } ], "patch": { "tags": [ - "Canned Response" + "Canned Responses" ], "operationId": "update-canned-response-in-account", "summary": "Update Canned Response in Account", "description": "Update a Canned Response in Account", "security": [ { - "userApiKey": [ - - ] + "userApiKey": [] } ], "parameters": [ { "in": "path", "name": "id", - "type": "integer", + "schema": { + "type": "integer" + }, "required": true, "description": "The ID of the canned response to be updated." - }, - { - "name": "data", - "in": "body", - "required": true, - "schema": { - "$ref": "#/definitions/canned_response_create_update_payload" - } } ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/canned_response_create_update_payload" + } + } + } + }, "responses": { "200": { "description": "Success", - "schema": { - "$ref": "#/definitions/canned_response" + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/canned_response" + } + } } }, "404": { - "description": "Agent not found" + "description": "Agent not found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/bad_request_error" + } + } + } }, "403": { - "description": "Access denied" + "description": "Access denied", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/bad_request_error" + } + } + } } } }, @@ -1748,16 +2090,16 @@ "description": "Remove a Canned Response from Account", "security": [ { - "userApiKey": [ - - ] + "userApiKey": [] } ], "parameters": [ { "in": "path", "name": "id", - "type": "integer", + "schema": { + "type": "integer" + }, "required": true, "description": "The ID of the canned response to be deleted" } @@ -1767,10 +2109,24 @@ "description": "Success" }, "404": { - "description": "Canned Response not found" + "description": "Canned Response not found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/bad_request_error" + } + } + } }, "403": { - "description": "Access denied" + "description": "Access denied", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/bad_request_error" + } + } + } } } } @@ -1778,7 +2134,7 @@ "/api/v1/accounts/{account_id}/custom_attribute_definitions": { "parameters": [ { - "$ref": "#/parameters/account_id" + "$ref": "#/components/parameters/account_id" } ], "get": { @@ -1791,11 +2147,13 @@ { "name": "attribute_model", "in": "query", - "type": "string", - "enum": [ - "0", - "1" - ], + "schema": { + "type": "string", + "enum": [ + "0", + "1" + ] + }, "description": "conversation_attribute(0)/contact_attribute(1)", "required": true } @@ -1803,24 +2161,33 @@ "description": "Get details of custom attributes in an Account", "security": [ { - "userApiKey": [ - - ] + "userApiKey": [] } ], "responses": { "200": { "description": "Success", - "schema": { - "type": "array", - "description": "Array of all custom attributes", - "items": { - "$ref": "#/definitions/custom_attribute" + "content": { + "application/json": { + "schema": { + "type": "array", + "description": "Array of all custom attributes", + "items": { + "$ref": "#/components/schemas/custom_attribute" + } + } } } }, "403": { - "description": "Access denied" + "description": "Access denied", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/bad_request_error" + } + } + } } } }, @@ -1833,30 +2200,39 @@ "description": "Add a new custom attribute to account", "security": [ { - "userApiKey": [ - - ] + "userApiKey": [] } ], - "parameters": [ - { - "name": "data", - "in": "body", - "required": true, - "schema": { - "$ref": "#/definitions/custom_attribute_create_update_payload" + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/custom_attribute_create_update_payload" + } } } - ], + }, "responses": { "200": { "description": "Success", - "schema": { - "$ref": "#/definitions/custom_attribute" + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/custom_attribute" + } + } } }, "403": { - "description": "Access denied" + "description": "Access denied", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/bad_request_error" + } + } + } } } } @@ -1864,12 +2240,14 @@ "/api/v1/accounts/{account_id}/custom_attribute_definitions/{id}": { "parameters": [ { - "$ref": "#/parameters/account_id" + "$ref": "#/components/parameters/account_id" }, { "name": "id", "in": "path", - "type": "number", + "schema": { + "type": "number" + }, "description": "ID of the custom attribute", "required": true } @@ -1880,15 +2258,22 @@ ], "operationId": "get-details-of-a-single-custom-attribute", "summary": "Get a custom attribute details", + "security": [ + { + "userApiKey": [] + } + ], "description": "Get the details of a custom attribute in the account", "parameters": [ { - "$ref": "#/parameters/account_id" + "$ref": "#/components/parameters/account_id" }, { "in": "path", "name": "id", - "type": "integer", + "schema": { + "type": "integer" + }, "required": true, "description": "The ID of the custom attribute to be updated." } @@ -1896,15 +2281,33 @@ "responses": { "200": { "description": "Success", - "schema": { - "$ref": "#/definitions/custom_attribute" + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/custom_attribute" + } + } } }, "401": { - "description": "Unauthorized" + "description": "Unauthorized", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/bad_request_error" + } + } + } }, "404": { - "description": "The given attribute ID does not exist in the account" + "description": "The given attribute ID does not exist in the account", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/bad_request_error" + } + } + } } } }, @@ -1917,40 +2320,60 @@ "description": "Update a custom attribute in account", "security": [ { - "userApiKey": [ - - ] + "userApiKey": [] } ], "parameters": [ { "in": "path", "name": "id", - "type": "integer", + "schema": { + "type": "integer" + }, "required": true, "description": "The ID of the custom attribute to be updated." - }, - { - "name": "data", - "in": "body", - "required": true, - "schema": { - "$ref": "#/definitions/custom_attribute_create_update_payload" - } } ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/custom_attribute_create_update_payload" + } + } + } + }, "responses": { "200": { "description": "Success", - "schema": { - "$ref": "#/definitions/custom_attribute" + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/custom_attribute" + } + } } }, "404": { - "description": "Agent not found" + "description": "Agent not found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/bad_request_error" + } + } + } }, "403": { - "description": "Access denied" + "description": "Access denied", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/bad_request_error" + } + } + } } } }, @@ -1963,19 +2386,19 @@ "description": "Remove a custom attribute from account", "security": [ { - "userApiKey": [ - - ] + "userApiKey": [] } ], "parameters": [ { - "$ref": "#/parameters/account_id" + "$ref": "#/components/parameters/account_id" }, { "in": "path", "name": "id", - "type": "integer", + "schema": { + "type": "integer" + }, "required": true, "description": "The ID of the custom attribute to be deleted" } @@ -1985,10 +2408,24 @@ "description": "Success" }, "404": { - "description": "Custom attribute not found" + "description": "Custom attribute not found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/bad_request_error" + } + } + } }, "403": { - "description": "Access denied" + "description": "Access denied", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/bad_request_error" + } + } + } } } } @@ -1999,30 +2436,43 @@ "Contacts" ], "operationId": "contactList", - "description": "Listing all the resolved contacts with pagination (Page size = 15) . Resolved contacts are the ones with a value for identifier, email or phone number", + "description": "Listing all the resolved contacts with pagination (Page size = 15). Resolved contacts are the ones with a value for identifier, email or phone number", "summary": "List Contacts", + "security": [ + { + "userApiKey": [] + } + ], "parameters": [ { - "$ref": "#/parameters/account_id" + "$ref": "#/components/parameters/account_id" }, { - "$ref": "#/parameters/contact_sort_param" + "$ref": "#/components/parameters/contact_sort_param" }, { - "$ref": "#/parameters/page" + "$ref": "#/components/parameters/page" } ], "responses": { "200": { "description": "Success", - "schema": { - "$ref": "#/definitions/contact_list" + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/contacts_list_response" + } + } } }, "400": { "description": "Bad Request Error", - "schema": { - "$ref": "#/definitions/bad_request_error" + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/bad_request_error" + } + } } } } @@ -2034,30 +2484,45 @@ "operationId": "contactCreate", "description": "Create a new Contact", "summary": "Create Contact", - "parameters": [ + "security": [ { - "$ref": "#/parameters/account_id" - }, - { - "name": "data", - "in": "body", - "required": true, - "schema": { - "$ref": "#/definitions/contact_create" - } + "userApiKey": [] } ], + "parameters": [ + { + "$ref": "#/components/parameters/account_id" + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/contact_create_payload" + } + } + } + }, "responses": { "200": { "description": "Success", - "schema": { - "$ref": "#/definitions/extended_contact" + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/extended_contact" + } + } } }, "400": { "description": "Bad Request Error", - "schema": { - "$ref": "#/definitions/bad_request_error" + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/bad_request_error" + } + } } } } @@ -2066,14 +2531,16 @@ "/api/v1/accounts/{account_id}/contacts/{id}": { "parameters": [ { - "$ref": "#/parameters/account_id" + "$ref": "#/components/parameters/account_id" }, { "name": "id", "in": "path", - "type": "number", - "description": "ID of the contact", - "required": true + "required": true, + "schema": { + "type": "number" + }, + "description": "ID of the contact" } ], "get": { @@ -2082,19 +2549,42 @@ ], "operationId": "contactDetails", "summary": "Show Contact", + "security": [ + { + "userApiKey": [] + } + ], "description": "Get a contact belonging to the account using ID", "responses": { "200": { "description": "Success", - "schema": { - "$ref": "#/definitions/extended_contact" + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/contact_show_response" + } + } } }, "404": { - "description": "Contact not found" + "description": "Contact not found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/bad_request_error" + } + } + } }, "403": { - "description": "Access denied" + "description": "Access denied", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/bad_request_error" + } + } + } } } }, @@ -2104,29 +2594,52 @@ ], "operationId": "contactUpdate", "summary": "Update Contact", - "description": "Update a contact belonging to the account using ID", - "parameters": [ + "security": [ { - "name": "data", - "in": "body", - "required": true, - "schema": { - "$ref": "#/definitions/contact_update" - } + "userApiKey": [] } ], + "description": "Update a contact belonging to the account using ID", + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/contact_update_payload" + } + } + } + }, "responses": { "204": { "description": "Success", - "schema": { - "$ref": "#/definitions/contact_base" + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/contact_base" + } + } } }, "404": { - "description": "Contact not found" + "description": "Contact not found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/bad_request_error" + } + } + } }, "403": { - "description": "Access denied" + "description": "Access denied", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/bad_request_error" + } + } + } } } }, @@ -2136,51 +2649,239 @@ ], "operationId": "contactDelete", "summary": "Delete Contact", + "security": [ + { + "userApiKey": [] + } + ], + "description": "Delete a contact belonging to the account using ID", "responses": { "200": { "description": "Success" }, "401": { - "description": "Unauthorized" + "description": "Unauthorized", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/bad_request_error" + } + } + } }, "404": { - "description": "Contact not found" + "description": "Contact not found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/bad_request_error" + } + } + } } } } }, "/api/v1/accounts/{account_id}/contacts/{id}/conversations": { + "parameters": [ + { + "$ref": "#/components/parameters/account_id" + }, + { + "name": "id", + "in": "path", + "required": true, + "schema": { + "type": "number" + }, + "description": "ID of the contact" + } + ], "get": { "tags": [ "Contacts" ], "operationId": "contactConversations", "summary": "Contact Conversations", - "description": "Get conversations associated to that contact", + "description": "Get conversations associated with that contact", "parameters": [ - { - "$ref": "#/parameters/account_id" - }, { "name": "id", "in": "path", - "type": "number", - "description": "ID of the contact", - "required": true + "required": true, + "schema": { + "type": "number" + }, + "description": "ID of the contact" + } + ], + "security": [ + { + "userApiKey": [] } ], "responses": { "200": { "description": "Success", - "schema": { - "$ref": "#/definitions/contact_conversations" + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/contact_conversations_response" + } + } } }, "404": { - "description": "Contact not found" + "description": "Contact not found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/bad_request_error" + } + } + } }, "403": { - "description": "Access denied" + "description": "Access denied", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/bad_request_error" + } + } + } + } + } + } + }, + "/api/v1/accounts/{account_id}/contacts/{id}/labels": { + "parameters": [ + { + "$ref": "#/components/parameters/account_id" + }, + { + "name": "id", + "in": "path", + "required": true, + "schema": { + "type": "number" + }, + "description": "ID of the contact" + } + ], + "get": { + "tags": [ + "Contact Labels" + ], + "operationId": "list-all-labels-of-a-contact", + "summary": "List Labels", + "description": "Lists all the labels of a contact", + "security": [ + { + "userApiKey": [] + } + ], + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/contact_labels" + } + } + } + }, + "401": { + "description": "Unauthorized", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/bad_request_error" + } + } + } + }, + "404": { + "description": "Contact not found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/bad_request_error" + } + } + } + } + } + }, + "post": { + "tags": [ + "Contact Labels" + ], + "operationId": "contact-add-labels", + "summary": "Add Labels", + "description": "Add labels to a contact. Note that this API would overwrite the existing list of labels associated to the conversation.", + "security": [ + { + "userApiKey": [] + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "labels" + ], + "properties": { + "labels": { + "type": "array", + "description": "Array of labels (comma-separated strings)", + "items": { + "type": "string" + }, + "example": [ + "support", + "billing" + ] + } + } + } + } + } + }, + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/contact_labels" + } + } + } + }, + "401": { + "description": "Unauthorized", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/bad_request_error" + } + } + } + }, + "404": { + "description": "Contact not found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/bad_request_error" + } + } + } } } } @@ -2193,50 +2894,55 @@ "operationId": "contactSearch", "description": "Search the resolved contacts using a search key, currently supports email search (Page size = 15). Resolved contacts are the ones with a value for identifier, email or phone number", "summary": "Search Contacts", + "security": [ + { + "userApiKey": [] + } + ], "parameters": [ { - "$ref": "#/parameters/account_id" + "$ref": "#/components/parameters/account_id" }, { "name": "q", "in": "query", - "type": "string", + "schema": { + "type": "string" + }, "description": "Search using contact `name`, `identifier`, `email` or `phone number`" }, { - "$ref": "#/parameters/contact_sort_param" + "$ref": "#/components/parameters/contact_sort_param" }, { - "$ref": "#/parameters/page" + "$ref": "#/components/parameters/page" } ], "responses": { "200": { "description": "Success", - "schema": { - "type": "object", - "properties": { - "payload": { - "$ref": "#/definitions/contact_list" + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/contacts_list_response" } } } }, "401": { "description": "Authentication error", - "schema": { - "$ref": "#/definitions/bad_request_error" + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/bad_request_error" + } + } } } } } }, "/api/v1/accounts/{account_id}/contacts/filter": { - "parameters": [ - { - "$ref": "#/parameters/account_id" - } - ], "post": { "tags": [ "Contacts" @@ -2246,99 +2952,107 @@ "summary": "Contact Filter", "security": [ { - "userApiKey": [ - - ] - }, - { - "agentBotApiKey": [ - - ] + "userApiKey": [] } ], "parameters": [ { - "name": "page", - "in": "query", - "type": "integer" + "$ref": "#/components/parameters/account_id" }, { - "name": "body", - "in": "body", - "required": true, + "name": "page", + "in": "query", "schema": { - "type": "object", - "properties": { - "payload": { - "type": "array", - "items": { - "type": "object", - "properties": { - "attribute_key": { - "type": "string", - "description": "filter attribute name" - }, - "filter_operator": { - "type": "string", - "description": "filter operator name", - "enum": [ - "equal_to", - "not_equal_to", - "contains", - "does_not_contain" - ] - }, - "values": { - "type": "array", - "items": { - "type": "string" + "type": "number" + } + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "payload": { + "type": "array", + "items": { + "type": "object", + "properties": { + "attribute_key": { + "type": "string", + "description": "filter attribute name" }, - "description": "array of the attribute values to filter" - }, - "query_operator": { - "type": "string", - "description": "query operator name", - "enum": [ - "AND", - "OR" - ] + "filter_operator": { + "type": "string", + "description": "filter operator name", + "enum": [ + "equal_to", + "not_equal_to", + "contains", + "does_not_contain" + ] + }, + "values": { + "type": "array", + "items": { + "type": "string" + }, + "description": "array of the attribute values to filter" + }, + "query_operator": { + "type": "string", + "description": "query operator name", + "enum": [ + "AND", + "OR" + ] + } } - } - }, - "example": [ - { - "attribute_key": "name", - "filter_operator": "equal_to", - "values": [ - "en" - ], - "query_operator": "AND" }, - { - "attribute_key": "country_code", - "filter_operator": "equal_to", - "values": [ - "us" - ], - "query_operator": null - } - ] + "example": [ + { + "attribute_key": "name", + "filter_operator": "equal_to", + "values": [ + "en" + ], + "query_operator": "AND" + }, + { + "attribute_key": "country_code", + "filter_operator": "equal_to", + "values": [ + "us" + ], + "query_operator": null + } + ] + } } } } } - ], + }, "responses": { "200": { "description": "Success", - "schema": { - "$ref": "#/definitions/contact_list" + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/contacts_list_response" + } + } } }, "400": { "description": "Bad Request Error", - "schema": { - "$ref": "#/definitions/bad_request_error" + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/bad_request_error" + } + } } } } @@ -2354,52 +3068,77 @@ "summary": "Create contact inbox", "parameters": [ { - "$ref": "#/parameters/account_id" + "$ref": "#/components/parameters/account_id" }, { "name": "id", "in": "path", - "type": "number", + "schema": { + "type": "number" + }, "description": "ID of the contact", "required": true - }, + } + ], + "security": [ { - "name": "data", - "in": "body", - "required": true, - "schema": { - "type": "object", - "required": [ - "inbox_id" - ], - "properties": { - "inbox_id": { - "type": "number", - "description": "The ID of the inbox" - }, - "source_id": { - "type": "string", - "description": "Contact Inbox Source Id" + "userApiKey": [] + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "inbox_id" + ], + "properties": { + "inbox_id": { + "type": "number", + "description": "The ID of the inbox", + "example": 1 + }, + "source_id": { + "type": "string", + "description": "Contact Inbox Source Id" + } } } } } - ], + }, "responses": { "200": { "description": "Success", - "schema": { - "$ref": "#/definitions/contact_inboxes" + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/contact_inboxes" + } + } } }, "401": { "description": "Authentication error", - "schema": { - "$ref": "#/definitions/bad_request_error" + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/bad_request_error" + } + } } }, "422": { - "description": "Incorrect payload" + "description": "Incorrect payload", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/bad_request_error" + } + } + } } } } @@ -2412,14 +3151,21 @@ "operationId": "contactableInboxesGet", "description": "Get List of contactable Inboxes", "summary": "Get Contactable Inboxes", + "security": [ + { + "userApiKey": [] + } + ], "parameters": [ { - "$ref": "#/parameters/account_id" + "$ref": "#/components/parameters/account_id" }, { "name": "id", "in": "path", - "type": "number", + "schema": { + "type": "number" + }, "description": "ID of the contact", "required": true } @@ -2427,18 +3173,33 @@ "responses": { "200": { "description": "Success", - "schema": { - "$ref": "#/definitions/contactable_inboxes" + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/contactable_inboxes_response" + } + } } }, "401": { "description": "Authentication error", - "schema": { - "$ref": "#/definitions/bad_request_error" + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/bad_request_error" + } + } } }, "422": { - "description": "Incorrect payload" + "description": "Incorrect payload", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/bad_request_error" + } + } + } } } } @@ -2446,7 +3207,7 @@ "/api/v1/accounts/{account_id}/automation_rules": { "parameters": [ { - "$ref": "#/parameters/account_id" + "$ref": "#/components/parameters/account_id" } ], "get": { @@ -2457,33 +3218,38 @@ "summary": "List all automation rules in an account", "parameters": [ { - "$ref": "#/parameters/account_id" + "$ref": "#/components/parameters/account_id" }, { - "$ref": "#/parameters/page" + "$ref": "#/components/parameters/page" } ], "description": "Get details of automation rules in an Account", "security": [ { - "userApiKey": [ - - ] + "userApiKey": [] } ], "responses": { "200": { "description": "Success", - "schema": { - "type": "array", - "description": "Array of all automation rules", - "items": { - "$ref": "#/definitions/automation_rule" + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/automation_rule" + } } } }, "403": { - "description": "Access denied" + "description": "Access denied", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/bad_request_error" + } + } + } } } }, @@ -2496,30 +3262,39 @@ "description": "Add a new automation rule to account", "security": [ { - "userApiKey": [ - - ] + "userApiKey": [] } ], - "parameters": [ - { - "name": "data", - "in": "body", - "required": true, - "schema": { - "$ref": "#/definitions/automation_rule_create_update_payload" + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/automation_rule_create_update_payload" + } } } - ], + }, "responses": { "200": { "description": "Success", - "schema": { - "$ref": "#/definitions/automation_rule" + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/automation_rule" + } + } } }, "403": { - "description": "Access denied" + "description": "Access denied", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/bad_request_error" + } + } + } } } } @@ -2527,12 +3302,14 @@ "/api/v1/accounts/{account_id}/automation_rules/{id}": { "parameters": [ { - "$ref": "#/parameters/account_id" + "$ref": "#/components/parameters/account_id" }, { "name": "id", "in": "path", - "type": "number", + "schema": { + "type": "number" + }, "description": "ID of the Automation Rule", "required": true } @@ -2544,11 +3321,18 @@ "operationId": "get-details-of-a-single-automation-rule", "summary": "Get a automation rule details", "description": "Get the details of a automation rule in the account", + "security": [ + { + "userApiKey": [] + } + ], "parameters": [ { "in": "path", "name": "id", - "type": "integer", + "schema": { + "type": "integer" + }, "required": true, "description": "The ID of the automation rule to be updated." } @@ -2556,15 +3340,70 @@ "responses": { "200": { "description": "Success", - "schema": { - "$ref": "#/definitions/automation_rule" + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/automation_rule" + }, + "example": { + "payload": { + "id": 90, + "account_id": 1, + "name": "add-label-bug-if-message-contains-bug", + "description": "add-label-bug-if-message-contains-bug", + "event_name": "message_created", + "conditions": [ + { + "values": [ + "incoming" + ], + "attribute_key": "message_type", + "query_operator": "and", + "filter_operator": "equal_to" + }, + { + "values": [ + "bug" + ], + "attribute_key": "content", + "filter_operator": "contains" + } + ], + "actions": [ + { + "action_name": "add_label", + "action_params": [ + "bugs", + "support-query" + ] + } + ], + "created_on": 1650555440, + "active": true + } + } + } } }, "401": { - "description": "Unauthorized" + "description": "Unauthorized", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/bad_request_error" + } + } + } }, "404": { - "description": "The given rule ID does not exist in the account" + "description": "The given rule ID does not exist in the account", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/bad_request_error" + } + } + } } } }, @@ -2577,40 +3416,60 @@ "description": "Update a automation rule in account", "security": [ { - "userApiKey": [ - - ] + "userApiKey": [] } ], "parameters": [ { "in": "path", "name": "id", - "type": "integer", + "schema": { + "type": "integer" + }, "required": true, "description": "The ID of the automation rule to be updated." - }, - { - "name": "data", - "in": "body", - "required": true, - "schema": { - "$ref": "#/definitions/automation_rule_create_update_payload" - } } ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/automation_rule_create_update_payload" + } + } + } + }, "responses": { "200": { "description": "Success", - "schema": { - "$ref": "#/definitions/automation_rule" + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/automation_rule" + } + } } }, "403": { - "description": "Access denied" + "description": "Access denied", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/bad_request_error" + } + } + } }, "404": { - "description": "Rule not found" + "description": "Rule not found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/bad_request_error" + } + } + } } } }, @@ -2623,16 +3482,16 @@ "description": "Remove a automation rule from account", "security": [ { - "userApiKey": [ - - ] + "userApiKey": [] } ], "parameters": [ { "in": "path", "name": "id", - "type": "integer", + "schema": { + "type": "integer" + }, "required": true, "description": "The ID of the automation rule to be deleted" } @@ -2642,10 +3501,24 @@ "description": "Success" }, "403": { - "description": "Access denied" + "description": "Access denied", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/bad_request_error" + } + } + } }, "404": { - "description": "automation rule not found" + "description": "automation rule not found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/bad_request_error" + } + } + } } } } @@ -2653,7 +3526,7 @@ "/api/v1/accounts/{account_id}/portals": { "parameters": [ { - "$ref": "#/parameters/account_id" + "$ref": "#/components/parameters/account_id" } ], "post": { @@ -2665,30 +3538,39 @@ "description": "Add a new portal to account", "security": [ { - "userApiKey": [ - - ] + "userApiKey": [] } ], - "parameters": [ - { - "name": "data", - "in": "body", - "required": true, - "schema": { - "$ref": "#/definitions/portal_create_update_payload" + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/portal_create_update_payload" + } } } - ], + }, "responses": { "200": { "description": "Success", - "schema": { - "$ref": "#/definitions/portal" + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/portal" + } + } } }, "403": { - "description": "Access denied" + "description": "Access denied", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/bad_request_error" + } + } + } } } }, @@ -2700,77 +3582,227 @@ "summary": "List all portals in an account", "parameters": [ { - "$ref": "#/parameters/account_id" + "$ref": "#/components/parameters/account_id" } ], "description": "Get details of portals in an Account", "security": [ { - "userApiKey": [ - - ] + "userApiKey": [] } ], "responses": { "200": { "description": "Success", - "schema": { - "type": "array", - "description": "Array of all portals", - "items": { - "$ref": "#/definitions/portal" + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/portal" + }, + "example": { + "payload": [ + { + "id": 4, + "color": "#1F93FF", + "custom_domain": "chatwoot.help", + "header_text": "Handbook", + "homepage_link": "https://www.chatwoot.com", + "name": "Handbook", + "page_title": "Handbook", + "slug": "handbook", + "archived": false, + "account_id": 1, + "config": { + "allowed_locales": [ + { + "code": "en", + "articles_count": 32, + "categories_count": 9 + } + ] + }, + "inbox": { + "id": 37, + "avatar_url": "https://example.com/avatar.png", + "channel_id": 1, + "name": "Chatwoot", + "channel_type": "Channel::WebWidget", + "greeting_enabled": true, + "widget_color": "#1F93FF", + "website_url": "chatwoot.com" + }, + "logo": { + "id": 19399916, + "portal_id": 4, + "file_type": "image/png", + "account_id": 1, + "file_url": "https://example.com/logo.png", + "blob_id": 21239614, + "filename": "square.png" + }, + "meta": { + "all_articles_count": 0, + "categories_count": 9, + "default_locale": "en" + } + } + ] + } } } }, "403": { - "description": "Access denied" - } - } - }, - "patch": { - "tags": [ - "Help Center" - ], - "operationId": "update-new-portal-to-account", - "summary": "update a new portal", - "description": "update a new portal to account", - "security": [ - { - "userApiKey": [ - - ] - } - ], - "parameters": [ - { - "name": "data", - "in": "body", - "required": true, - "schema": { - "$ref": "#/definitions/portal_create_update_payload" + "description": "Access denied", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/bad_request_error" + } + } } } - ], - "responses": { - "200": { - "description": "Success", - "schema": { - "$ref": "#/definitions/portal" - } - }, - "403": { - "description": "Access denied" - } } } }, - "/api/v1/accounts/{account_id}/portals/{portal_id}/categories": { + "/api/v1/accounts/{account_id}/portals/{id}": { "parameters": [ { - "$ref": "#/parameters/account_id" + "$ref": "#/components/parameters/account_id" }, { - "$ref": "#/parameters/portal_id" + "$ref": "#/components/parameters/portal_id" + } + ], + "patch": { + "tags": [ + "Help Center" + ], + "operationId": "update-portal-to-account", + "summary": "Update a portal", + "description": "Update a portal to account", + "security": [ + { + "userApiKey": [] + } + ], + "parameters": [ + { + "$ref": "#/components/parameters/account_id" + }, + { + "$ref": "#/components/parameters/portal_id" + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/portal_create_update_payload" + } + } + } + }, + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/portal_single" + }, + "example": { + "payload": { + "id": 123, + "archived": false, + "color": "#1F93FF", + "config": { + "allowed_locales": [ + { + "code": "en", + "articles_count": 32, + "categories_count": 9 + } + ] + }, + "custom_domain": "chatwoot.help", + "header_text": "Handbook", + "homepage_link": "https://www.chatwoot.com", + "name": "Handbook", + "slug": "handbook", + "page_title": "Handbook", + "account_id": 123, + "inbox": { + "id": 123, + "name": "Chatwoot", + "website_url": "chatwoot.com", + "channel_type": "Channel::WebWidget", + "avatar_url": "https://example.com/avatar.png", + "widget_color": "#1F93FF", + "website_token": "4cWzuf9i9jxN9tbnv8K9STKU", + "enable_auto_assignment": true, + "web_widget_script": "", + "welcome_title": "Hi there ! 🙌🏼", + "welcome_tagline": "We make it simple to connect with us.", + "greeting_enabled": true, + "greeting_message": "Hey there 👋, Thank you for reaching out to us.", + "channel_id": 123, + "working_hours_enabled": true, + "enable_email_collect": true, + "csat_survey_enabled": true, + "timezone": "America/Los_Angeles", + "business_name": "Chatwoot", + "hmac_mandatory": true + }, + "logo": { + "id": 123, + "portal_id": 123, + "file_type": "image/png", + "account_id": 123, + "file_url": "https://example.com/logo.png", + "blob_id": 123, + "filename": "square.png" + }, + "meta": { + "all_articles_count": 32, + "categories_count": 9, + "default_locale": "en" + } + } + } + } + } + }, + "403": { + "description": "Access denied", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/bad_request_error" + } + } + } + }, + "404": { + "description": "Portal not found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/bad_request_error" + } + } + } + } + } + } + }, + "/api/v1/accounts/{account_id}/portals/{id}/categories": { + "parameters": [ + { + "$ref": "#/components/parameters/account_id" + }, + { + "$ref": "#/components/parameters/portal_id" } ], "post": { @@ -2782,41 +3814,50 @@ "description": "Add a new category to portal", "security": [ { - "userApiKey": [ - - ] + "userApiKey": [] } ], - "parameters": [ - { - "name": "data", - "in": "body", - "required": true, - "schema": { - "$ref": "#/definitions/category_create_update_payload" + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/category_create_update_payload" + } } } - ], + }, "responses": { "200": { "description": "Success", - "schema": { - "$ref": "#/definitions/category" + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/category" + } + } } }, "403": { - "description": "Access denied" + "description": "Access denied", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/bad_request_error" + } + } + } } } } }, - "/api/v1/accounts/{account_id}/portals/{portal_id}/articles": { + "/api/v1/accounts/{account_id}/portals/{id}/articles": { "parameters": [ { - "$ref": "#/parameters/account_id" + "$ref": "#/components/parameters/account_id" }, { - "$ref": "#/parameters/portal_id" + "$ref": "#/components/parameters/portal_id" } ], "post": { @@ -2828,30 +3869,39 @@ "description": "Add a new article to portal", "security": [ { - "userApiKey": [ - - ] + "userApiKey": [] } ], - "parameters": [ - { - "name": "data", - "in": "body", - "required": true, - "schema": { - "$ref": "#/definitions/article_create_update_payload" + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/article_create_update_payload" + } } } - ], + }, "responses": { "200": { "description": "Success", - "schema": { - "$ref": "#/definitions/article" + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/article" + } + } } }, "403": { - "description": "Access denied" + "description": "Access denied", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/bad_request_error" + } + } + } } } } @@ -2859,7 +3909,7 @@ "/api/v1/accounts/{account_id}/conversations/meta": { "parameters": [ { - "$ref": "#/parameters/account_id" + "$ref": "#/components/parameters/account_id" } ], "get": { @@ -2869,66 +3919,85 @@ "operationId": "conversationListMeta", "description": "Get open, unassigned and all Conversation counts", "summary": "Get Conversation Counts", + "security": [ + { + "userApiKey": [] + } + ], "parameters": [ { "name": "status", "in": "query", - "type": "string", - "enum": [ - "all", - "open", - "resolved", - "pending", - "snoozed" - ], - "default": "open", + "schema": { + "type": "string", + "enum": [ + "all", + "open", + "resolved", + "pending", + "snoozed" + ], + "default": "open" + }, "description": "Filter by conversation status." }, { "name": "q", "in": "query", - "type": "string", + "schema": { + "type": "string" + }, "description": "Filters conversations with messages containing the search term" }, { "name": "inbox_id", "in": "query", - "type": "integer" + "schema": { + "type": "integer" + } }, { "name": "team_id", "in": "query", - "type": "integer" + "schema": { + "type": "integer" + } }, { "name": "labels", "in": "query", - "type": "array", - "items": { - "type": "string" + "schema": { + "type": "array", + "items": { + "type": "string" + } } } ], "responses": { "200": { "description": "Success", - "schema": { - "type": "object", - "properties": { - "meta": { + "content": { + "application/json": { + "schema": { "type": "object", "properties": { - "mine_count": { - "type": "number" - }, - "unassigned_count": { - "type": "number" - }, - "assigned_count": { - "type": "number" - }, - "all_count": { - "type": "number" + "meta": { + "type": "object", + "properties": { + "mine_count": { + "type": "number" + }, + "unassigned_count": { + "type": "number" + }, + "assigned_count": { + "type": "number" + }, + "all_count": { + "type": "number" + } + } } } } @@ -2937,8 +4006,12 @@ }, "400": { "description": "Bad Request Error", - "schema": { - "$ref": "#/definitions/bad_request_error" + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/bad_request_error" + } + } } } } @@ -2947,7 +4020,7 @@ "/api/v1/accounts/{account_id}/conversations": { "parameters": [ { - "$ref": "#/parameters/account_id" + "$ref": "#/components/parameters/account_id" } ], "get": { @@ -2957,77 +4030,104 @@ "operationId": "conversationList", "description": "List all the conversations with pagination", "summary": "Conversations List", + "security": [ + { + "userApiKey": [] + } + ], "parameters": [ { "name": "assignee_type", "in": "query", - "type": "string", - "enum": [ - "me", - "unassigned", - "all", - "assigned" - ], - "default": "all", + "schema": { + "type": "string", + "enum": [ + "me", + "unassigned", + "all", + "assigned" + ], + "default": "all" + }, "description": "Filter conversations by assignee type." }, { "name": "status", "in": "query", - "type": "string", - "enum": [ - "all", - "open", - "resolved", - "pending", - "snoozed" - ], - "default": "open", + "schema": { + "type": "string", + "enum": [ + "all", + "open", + "resolved", + "pending", + "snoozed" + ], + "default": "open" + }, "description": "Filter by conversation status." }, { "name": "q", "in": "query", - "type": "string", + "schema": { + "type": "string" + }, "description": "Filters conversations with messages containing the search term" }, { "name": "inbox_id", "in": "query", - "type": "integer" + "schema": { + "type": "integer" + } }, { "name": "team_id", "in": "query", - "type": "integer" + "schema": { + "type": "integer" + } }, { "name": "labels", "in": "query", - "type": "array", - "items": { - "type": "string" + "schema": { + "type": "array", + "items": { + "type": "string" + } } }, { "name": "page", "in": "query", - "type": "integer", - "default": 1, + "schema": { + "type": "integer", + "default": 1 + }, "description": "paginate through conversations" } ], "responses": { "200": { "description": "Success", - "schema": { - "$ref": "#/definitions/conversation_list" + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/conversation_list" + } + } } }, "400": { "description": "Bad Request Error", - "schema": { - "$ref": "#/definitions/bad_request_error" + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/bad_request_error" + } + } } } } @@ -3038,140 +4138,59 @@ ], "operationId": "newConversation", "summary": "Create New Conversation", - "description": "Creating a conversation in chatwoot requires a source id. \n\n Learn more about source_id: https://github.com/chatwoot/chatwoot/wiki/Building-on-Top-of-Chatwoot:-Importing-Existing-Contacts-and-Creating-Conversations", + "description": "Creating a conversation in chatwoot requires a source id. \n\n Learn more about source_id: https://www.chatwoot.com/hc/user-guide/articles/1677839703-how-to-create-an-api-channel-inbox#send-messages-to-the-api-channel", "security": [ { - "userApiKey": [ - - ] + "userApiKey": [] }, { - "agentBotApiKey": [ - - ] + "agentBotApiKey": [] } ], - "parameters": [ - { - "name": "data", - "in": "body", - "required": true, - "schema": { - "type": "object", - "required": [ - "source_id", - "inbox_id" - ], - "properties": { - "source_id": { - "type": "string", - "description": "Conversation source id" - }, - "inbox_id": { - "type": "string", - "description": "Id of inbox in which the conversation is created Allowed Inbox Types: Website, Phone, Api, Email " - }, - "contact_id": { - "type": "string", - "description": "Contact Id for which conversation is created" - }, - "additional_attributes": { + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/conversation_create_payload" + } + } + } + }, + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { "type": "object", - "description": "Lets you specify attributes like browser information" - }, - "custom_attributes": { - "type": "object", - "description": "The object to save custom attributes for conversation, accepts custom attributes key and value", - "example": { - "attribute_key": "attribute_value", - "priority_conversation_number": 3 - } - }, - "status": { - "type": "string", - "enum": [ - "open", - "resolved", - "pending" - ], - "description": "Specify the conversation whether it's pending, open, closed" - }, - "assignee_id": { - "type": "string", - "description": "Agent Id for assigning a conversation to an agent" - }, - "team_id": { - "type": "string", - "description": "Team Id for assigning a conversation to a team" - }, - "message": { - "type": "object", - "description": "The initial message to be sent to the conversation", - "required": [ - "content" - ], "properties": { - "content": { - "type": "string", - "description": "The content of the message" + "id": { + "type": "number", + "description": "ID of the conversation" }, - "template_params": { - "type": "object", - "description": "The template params for the message in case of whatsapp Channel", - "properties": { - "name": { - "type": "string", - "description": "Name of the template", - "example": "sample_issue_resolution" - }, - "category": { - "type": "string", - "description": "Category of the template", - "example": "UTILITY" - }, - "language": { - "type": "string", - "description": "Language of the template", - "example": "en_US" - }, - "processed_params": { - "type": "object", - "description": "The processed param values for template variables in template", - "example": { - "1": "Chatwoot" - } - } - } + "account_id": { + "type": "number", + "description": "Account Id" + }, + "inbox_id": { + "type": "number", + "description": "ID of the inbox" } } } } } - } - ], - "responses": { - "200": { - "description": "Success", - "schema": { - "type": "object", - "properties": { - "id": { - "type": "number", - "description": "ID of the conversation" - }, - "account_id": { - "type": "number", - "description": "Account Id" - }, - "inbox_id": { - "type": "number", - "description": "ID of the inbox" + }, + "403": { + "description": "Access denied", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/bad_request_error" } } } - }, - "403": { - "description": "Access denied" } } } @@ -3179,7 +4198,7 @@ "/api/v1/accounts/{account_id}/conversations/filter": { "parameters": [ { - "$ref": "#/parameters/account_id" + "$ref": "#/components/parameters/account_id" } ], "post": { @@ -3191,99 +4210,104 @@ "summary": "Conversations Filter", "security": [ { - "userApiKey": [ - - ] - }, - { - "agentBotApiKey": [ - - ] + "userApiKey": [] } ], "parameters": [ { "name": "page", "in": "query", - "type": "integer" - }, - { - "name": "body", - "in": "body", - "required": true, "schema": { - "type": "object", - "properties": { - "payload": { - "type": "array", - "items": { - "type": "object", - "properties": { - "attribute_key": { - "type": "string", - "description": "filter attribute name" - }, - "filter_operator": { - "type": "string", - "description": "filter operator name", - "enum": [ - "equal_to", - "not_equal_to", - "contains", - "does_not_contain" - ] - }, - "values": { - "type": "array", - "items": { - "type": "string" + "type": "number" + } + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "payload": { + "type": "array", + "items": { + "type": "object", + "properties": { + "attribute_key": { + "type": "string", + "description": "filter attribute name" }, - "description": "array of the attribute values to filter" - }, - "query_operator": { - "type": "string", - "description": "query operator name", - "enum": [ - "AND", - "OR" - ] + "filter_operator": { + "type": "string", + "description": "filter operator name", + "enum": [ + "equal_to", + "not_equal_to", + "contains", + "does_not_contain" + ] + }, + "values": { + "type": "array", + "items": { + "type": "string" + }, + "description": "array of the attribute values to filter" + }, + "query_operator": { + "type": "string", + "description": "query operator name", + "enum": [ + "AND", + "OR" + ] + } } - } - }, - "example": [ - { - "attribute_key": "browser_language", - "filter_operator": "not_eq", - "values": [ - "en" - ], - "query_operator": "AND" }, - { - "attribute_key": "status", - "filter_operator": "eq", - "values": [ - "pending" - ], - "query_operator": null - } - ] + "example": [ + { + "attribute_key": "browser_language", + "filter_operator": "not_equal_to", + "values": [ + "en" + ], + "query_operator": "AND" + }, + { + "attribute_key": "status", + "filter_operator": "equal_to", + "values": [ + "pending" + ], + "query_operator": null + } + ] + } } } } } - ], + }, "responses": { "200": { "description": "Success", - "schema": { - "$ref": "#/definitions/conversation_list" + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/conversation_list" + } + } } }, "400": { "description": "Bad Request Error", - "schema": { - "$ref": "#/definitions/bad_request_error" + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/bad_request_error" + } + } } } } @@ -3292,10 +4316,10 @@ "/api/v1/accounts/{account_id}/conversations/{conversation_id}": { "parameters": [ { - "$ref": "#/parameters/account_id" + "$ref": "#/components/parameters/account_id" }, { - "$ref": "#/parameters/conversation_id" + "$ref": "#/components/parameters/conversation_id" } ], "get": { @@ -3304,19 +4328,42 @@ ], "operationId": "get-details-of-a-conversation", "summary": "Conversation Details", + "security": [ + { + "userApiKey": [] + } + ], "description": "Get all details regarding a conversation with all messages in the conversation", "responses": { "200": { "description": "Success", - "schema": { - "$ref": "#/definitions/conversation_show" + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/conversation_show" + } + } + } + }, + "403": { + "description": "Access denied", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/bad_request_error" + } + } } }, "404": { - "description": "Conversation not found" - }, - "403": { - "description": "Access denied" + "description": "Conversation not found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/bad_request_error" + } + } + } } } }, @@ -3329,52 +4376,64 @@ "description": "Update Conversation Attributes", "security": [ { - "userApiKey": [ - - ] + "userApiKey": [] }, { - "agentBotApiKey": [ - - ] + "agentBotApiKey": [] } ], - "parameters": [ - { - "name": "data", - "in": "body", - "required": true, - "schema": { - "type": "object", - "properties": { - "priority": { - "type": "string", - "enum": [ - "urgent", - "high", - "medium", - "low", - "none" - ], - "description": "The priority of the conversation" - }, - "sla_policy_id": { - "type": "number", - "description": "The ID of the SLA policy (Available only in Enterprise edition)" + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "priority": { + "type": "string", + "enum": [ + "urgent", + "high", + "medium", + "low", + "none" + ], + "description": "The priority of the conversation", + "example": "high" + }, + "sla_policy_id": { + "type": "number", + "description": "The ID of the SLA policy (Available only in Enterprise edition)", + "example": 1 + } } } } } - ], + }, "responses": { "200": { "description": "Success" }, - "404": { - "description": "Conversation not found" - }, "401": { - "description": "Unauthorized" + "description": "Unauthorized", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/bad_request_error" + } + } + } + }, + "404": { + "description": "Conversation not found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/bad_request_error" + } + } + } } } } @@ -3382,10 +4441,10 @@ "/api/v1/accounts/{account_id}/conversations/{conversation_id}/toggle_status": { "parameters": [ { - "$ref": "#/parameters/account_id" + "$ref": "#/components/parameters/account_id" }, { - "$ref": "#/parameters/conversation_id" + "$ref": "#/components/parameters/conversation_id" } ], "post": { @@ -3397,52 +4456,67 @@ "description": "Toggles the status of the conversation between open and resolved", "security": [ { - "userApiKey": [ - - ] + "userApiKey": [] }, { - "agentBotApiKey": [ - - ] + "agentBotApiKey": [] } ], - "parameters": [ - { - "name": "data", - "in": "body", - "required": true, - "schema": { - "type": "object", - "required": [ - "status" - ], - "properties": { - "status": { - "type": "string", - "enum": [ - "open", - "resolved", - "pending" - ], - "description": "The status of the conversation" + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "status" + ], + "properties": { + "status": { + "type": "string", + "enum": [ + "open", + "resolved", + "pending" + ], + "description": "The status of the conversation", + "example": "open" + } } } } } - ], + }, "responses": { "200": { "description": "Success", - "schema": { - "$ref": "#/definitions/conversation_status_toggle" + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/conversation_status_toggle" + } + } } }, "404": { - "description": "Conversation not found" + "description": "Conversation not found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/bad_request_error" + } + } + } }, "401": { - "description": "Unauthorized" + "description": "Unauthorized", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/bad_request_error" + } + } + } } } } @@ -3450,10 +4524,10 @@ "/api/v1/accounts/{account_id}/conversations/{conversation_id}/toggle_priority": { "parameters": [ { - "$ref": "#/parameters/account_id" + "$ref": "#/components/parameters/account_id" }, { - "$ref": "#/parameters/conversation_id" + "$ref": "#/components/parameters/conversation_id" } ], "post": { @@ -3465,51 +4539,62 @@ "description": "Toggles the priority of conversation", "security": [ { - "userApiKey": [ - - ] + "userApiKey": [] }, { - "agentBotApiKey": [ - - ] + "agentBotApiKey": [] } ], - "parameters": [ - { - "name": "data", - "in": "body", - "required": true, - "schema": { - "type": "object", - "required": [ - "priority" - ], - "properties": { - "priority": { - "type": "string", - "enum": [ - "urgent", - "high", - "medium", - "low", - "none" - ], - "description": "The priority of the conversation" + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "priority" + ], + "properties": { + "priority": { + "type": "string", + "enum": [ + "urgent", + "high", + "medium", + "low", + "none" + ], + "description": "The priority of the conversation", + "example": "high" + } } } } } - ], + }, "responses": { "200": { "description": "Success" }, - "404": { - "description": "Conversation not found" - }, "401": { - "description": "Unauthorized" + "description": "Unauthorized", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/bad_request_error" + } + } + } + }, + "404": { + "description": "Conversation not found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/bad_request_error" + } + } + } } } } @@ -3517,10 +4602,10 @@ "/api/v1/accounts/{account_id}/conversations/{conversation_id}/custom_attributes": { "parameters": [ { - "$ref": "#/parameters/account_id" + "$ref": "#/components/parameters/account_id" }, { - "$ref": "#/parameters/conversation_id" + "$ref": "#/components/parameters/conversation_id" } ], "post": { @@ -3532,57 +4617,68 @@ "description": "Updates the custom attributes of a conversation", "security": [ { - "userApiKey": [ - - ] - }, - { - "agentBotApiKey": [ - - ] + "userApiKey": [] } ], - "parameters": [ - { - "name": "data", - "in": "body", - "required": true, - "schema": { - "type": "object", - "required": [ - "custom_attributes" - ], - "properties": { - "custom_attributes": { - "type": "object", - "description": "The custom attributes to be set for the conversation", - "example": { - "order_id": "12345", - "previous_conversation": "67890" + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "custom_attributes" + ], + "properties": { + "custom_attributes": { + "type": "object", + "description": "The custom attributes to be set for the conversation", + "example": { + "order_id": "12345", + "previous_conversation": "67890" + } } } } } } - ], + }, "responses": { "200": { "description": "Success", - "schema": { - "type": "object", - "properties": { - "custom_attributes": { + "content": { + "application/json": { + "schema": { "type": "object", - "description": "The custom attributes of the conversation" + "properties": { + "custom_attributes": { + "type": "object", + "description": "The custom attributes of the conversation" + } + } + } + } + } + }, + "401": { + "description": "Unauthorized", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/bad_request_error" } } } }, "404": { - "description": "Conversation not found" - }, - "401": { - "description": "Unauthorized" + "description": "Conversation not found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/bad_request_error" + } + } + } } } } @@ -3590,63 +4686,79 @@ "/api/v1/accounts/{account_id}/conversations/{conversation_id}/assignments": { "parameters": [ { - "$ref": "#/parameters/account_id" + "$ref": "#/components/parameters/account_id" }, { - "$ref": "#/parameters/conversation_id" + "$ref": "#/components/parameters/conversation_id" } ], "post": { "tags": [ - "Conversation Assignment" + "Conversation Assignments" ], "operationId": "assign-a-conversation", "summary": "Assign Conversation", "description": "Assign a conversation to an agent or a team", "security": [ { - "userApiKey": [ - - ] + "userApiKey": [] }, { - "agentBotApiKey": [ - - ] + "agentBotApiKey": [] } ], - "parameters": [ - { - "name": "data", - "in": "body", - "required": true, - "schema": { - "type": "object", - "properties": { - "assignee_id": { - "type": "number", - "description": "Id of the assignee user" - }, - "team_id": { - "type": "number", - "description": "Id of the team. If the assignee_id is present, this param would be ignored" + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "assignee_id": { + "type": "number", + "description": "Id of the assignee user", + "example": 1 + }, + "team_id": { + "type": "number", + "description": "Id of the team. If the assignee_id is present, this param would be ignored", + "example": 1 + } } } } } - ], + }, "responses": { "200": { "description": "Success", - "schema": { - "$ref": "#/definitions/user" + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/user" + } + } + } + }, + "401": { + "description": "Unauthorized", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/bad_request_error" + } + } } }, "404": { - "description": "Conversation not found" - }, - "401": { - "description": "Unauthorized" + "description": "Conversation not found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/bad_request_error" + } + } + } } } } @@ -3654,72 +4766,125 @@ "/api/v1/accounts/{account_id}/conversations/{conversation_id}/labels": { "parameters": [ { - "$ref": "#/parameters/account_id" + "$ref": "#/components/parameters/account_id" }, { - "$ref": "#/parameters/conversation_id" + "$ref": "#/components/parameters/conversation_id" } ], "get": { "tags": [ - "Conversation Labels" + "Conversations" ], "operationId": "list-all-labels-of-a-conversation", "summary": "List Labels", + "security": [ + { + "userApiKey": [] + } + ], "description": "Lists all the labels of a conversation", "responses": { "200": { "description": "Success", - "schema": { - "$ref": "#/definitions/conversation_labels" + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/conversation_labels" + } + } } }, "404": { - "description": "Conversation not found" + "description": "Conversation not found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/bad_request_error" + } + } + } }, "401": { - "description": "Unauthorized" + "description": "Unauthorized", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/bad_request_error" + } + } + } } } }, "post": { "tags": [ - "Conversation Labels" + "Conversations" ], "operationId": "conversation-add-labels", "summary": "Add Labels", - "description": "Add labels to a conversation. Note that this API would overwrite the existing list of labels associated to the conversation.", - "parameters": [ + "security": [ { - "name": "data", - "in": "body", - "required": true, - "schema": { - "type": "object", - "properties": { - "labels": { - "type": "array", - "description": "Array of labels (comma-separated strings)", - "items": { - "type": "string" + "userApiKey": [] + } + ], + "description": "Add labels to a conversation. Note that this API would overwrite the existing list of labels associated to the conversation.", + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "labels" + ], + "properties": { + "labels": { + "type": "array", + "description": "Array of labels (comma-separated strings)", + "items": { + "type": "string" + }, + "example": [ + "support", + "billing" + ] } } } } } - ], + }, "responses": { "200": { "description": "Success", - "schema": { - "$ref": "#/definitions/conversation_labels" + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/conversation_labels" + } + } } }, "404": { - "description": "Conversation not found" + "description": "Conversation not found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/bad_request_error" + } + } + } }, "401": { - "description": "Unauthorized" + "description": "Unauthorized", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/bad_request_error" + } + } + } } } } @@ -3732,27 +4897,55 @@ "operationId": "listAllInboxes", "summary": "List all inboxes", "description": "List all inboxes available in the current account", + "security": [ + { + "userApiKey": [] + } + ], "parameters": [ { - "$ref": "#/parameters/account_id" + "$ref": "#/components/parameters/account_id" } ], "responses": { "200": { "description": "Success", - "schema": { - "type": "array", - "description": "Array of inboxes", - "items": { - "$ref": "#/definitions/inbox" + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "payload": { + "type": "array", + "description": "Array of inboxes", + "items": { + "$ref": "#/components/schemas/inbox" + } + } + } + } } } }, "404": { - "description": "Inbox not found" + "description": "Inbox not found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/bad_request_error" + } + } + } }, "403": { - "description": "Access denied" + "description": "Access denied", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/bad_request_error" + } + } + } } } } @@ -3764,15 +4957,22 @@ ], "operationId": "GetInbox", "summary": "Get an inbox", + "security": [ + { + "userApiKey": [] + } + ], "description": "Get an inbox available in the current account", "parameters": [ { - "$ref": "#/parameters/account_id" + "$ref": "#/components/parameters/account_id" }, { "name": "id", "in": "path", - "type": "number", + "schema": { + "type": "number" + }, "description": "ID of the inbox", "required": true } @@ -3780,15 +4980,33 @@ "responses": { "200": { "description": "Success", - "schema": { - "$ref": "#/definitions/inbox" + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/inbox" + } + } } }, "404": { - "description": "Inbox not found" + "description": "Inbox not found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/bad_request_error" + } + } + } }, "403": { - "description": "Access denied" + "description": "Access denied", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/bad_request_error" + } + } + } } } } @@ -3801,73 +5019,56 @@ "operationId": "inboxCreation", "summary": "Create an inbox", "description": "You can create more than one website inbox in each account", + "security": [ + { + "userApiKey": [] + } + ], "parameters": [ { - "$ref": "#/parameters/account_id" - }, - { - "name": "data", - "in": "body", - "required": true, - "schema": { - "type": "object", - "properties": { - "name": { - "type": "string", - "description": "The name of the inbox" - }, - "avatar": { - "type": "string", - "format": "binary", - "description": "File for avatar image" - }, - "channel": { - "type": "object", - "properties": { - "type": { - "type": "string", - "enum": [ - "web_widget" - ] - }, - "website_url": { - "type": "string", - "description": "URL at which the widget will be loaded" - }, - "welcome_title": { - "type": "string", - "description": "Welcome title to be displayed on the widget" - }, - "welcome_tagline": { - "type": "string", - "description": "Welcome tagline to be displayed on the widget" - }, - "agent_away_message": { - "type": "string", - "description": "A message which will be sent if there is not agent available. This is not available if agentbot is connected" - }, - "widget_color": { - "type": "string", - "description": "A Hex-color string used to customize the widget" - } - } - } + "$ref": "#/components/parameters/account_id" + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/inbox_create_payload" } } } - ], + }, "responses": { "200": { "description": "Success", - "schema": { - "$ref": "#/definitions/inbox" + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/inbox" + } + } } }, "404": { - "description": "Inbox not found" + "description": "Inbox not found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/bad_request_error" + } + } + } }, "403": { - "description": "Access denied" + "description": "Access denied", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/bad_request_error" + } + } + } } } } @@ -3879,82 +5080,66 @@ ], "operationId": "updateInbox", "summary": "Update Inbox", - "description": "Add avatar and disable auto assignment for an inbox", + "security": [ + { + "userApiKey": [] + } + ], + "description": "Update an existing inbox", "parameters": [ { - "$ref": "#/parameters/account_id" + "$ref": "#/components/parameters/account_id" }, { "name": "id", "in": "path", - "type": "number", + "schema": { + "type": "number" + }, "description": "ID of the inbox", "required": true - }, - { - "name": "data", - "in": "body", - "required": true, - "schema": { - "type": "object", - "required": [ - "enable_auto_assignment" - ], - "properties": { - "name": { - "type": "string", - "description": "The name of the inbox" - }, - "enable_auto_assignment": { - "type": "boolean", - "description": "Enable Auto Assignment" - }, - "avatar": { - "type": "string", - "format": "binary", - "description": "Image file for avatar" - }, - "channel": { - "type": "object", - "properties": { - "website_url": { - "type": "string", - "description": "URL at which the widget will be loaded" - }, - "welcome_title": { - "type": "string", - "description": "Welcome title to be displayed on the widget" - }, - "welcome_tagline": { - "type": "string", - "description": "Welcome tagline to be displayed on the widget" - }, - "agent_away_message": { - "type": "string", - "description": "A message which will be sent if there is not agent available. This is not available if agentbot is connected" - }, - "widget_color": { - "type": "string", - "description": "A Hex-color string used to customize the widget" - } - } - } + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/inbox_update_payload" } } } - ], + }, "responses": { "200": { "description": "Success", - "schema": { - "$ref": "#/definitions/inbox" + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/inbox" + } + } } }, "404": { - "description": "Inbox not found" + "description": "Inbox not found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/bad_request_error" + } + } + } }, "403": { - "description": "Access denied" + "description": "Access denied", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/bad_request_error" + } + } + } } } } @@ -3967,14 +5152,21 @@ "operationId": "getInboxAgentBot", "summary": "Show Inbox Agent Bot", "description": "See if an agent bot is associated to the Inbox", + "security": [ + { + "userApiKey": [] + } + ], "parameters": [ { - "$ref": "#/parameters/account_id" + "$ref": "#/components/parameters/account_id" }, { "name": "id", "in": "path", - "type": "number", + "schema": { + "type": "number" + }, "description": "ID of the inbox", "required": true } @@ -3982,15 +5174,33 @@ "responses": { "204": { "description": "Success", - "schema": { - "$ref": "#/definitions/agent_bot" + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/agent_bot" + } + } } }, "404": { - "description": "Inbox not found, Agent bot not found" + "description": "Inbox not found, Agent bot not found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/bad_request_error" + } + } + } }, "403": { - "description": "Access denied" + "description": "Access denied", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/bad_request_error" + } + } + } } } } @@ -4002,45 +5212,69 @@ ], "operationId": "updateAgentBot", "summary": "Add or remove agent bot", + "security": [ + { + "userApiKey": [] + } + ], "description": "To add an agent bot pass agent_bot id, to remove agent bot from an inbox pass null", "parameters": [ { - "$ref": "#/parameters/account_id" + "$ref": "#/components/parameters/account_id" }, { "name": "id", "in": "path", - "type": "number", + "schema": { + "type": "number" + }, "description": "ID of the inbox", "required": true - }, - { - "name": "data", - "in": "body", - "required": true, - "schema": { - "type": "object", - "required": [ - "agent_bot" - ], - "properties": { - "agent_bot": { - "type": "number", - "description": "Agent bot ID" + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "agent_bot" + ], + "properties": { + "agent_bot": { + "type": "number", + "description": "Agent bot ID", + "example": 1 + } } } } } - ], + }, "responses": { "204": { "description": "Success" }, "404": { - "description": "Inbox not found, Agent bot not found" + "description": "Inbox not found, Agent bot not found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/bad_request_error" + } + } + } }, "403": { - "description": "Access denied" + "description": "Access denied", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/bad_request_error" + } + } + } } } } @@ -4048,10 +5282,10 @@ "/api/v1/accounts/{account_id}/inbox_members/{inbox_id}": { "parameters": [ { - "$ref": "#/parameters/account_id" + "$ref": "#/components/parameters/account_id" }, { - "$ref": "#/parameters/inbox_id" + "$ref": "#/components/parameters/inbox_id" } ], "get": { @@ -4063,32 +5297,53 @@ "description": "Get Details of Agents in an Inbox", "security": [ { - "userApiKey": [ - - ] + "userApiKey": [] } ], "parameters": [ { - "$ref": "#/parameters/inbox_id" + "$ref": "#/components/parameters/inbox_id" } ], "responses": { "200": { "description": "Success", - "schema": { - "type": "array", - "description": "Array of all active agents", - "items": { - "$ref": "#/definitions/agent" + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "payload": { + "type": "array", + "description": "Array of all active agents", + "items": { + "$ref": "#/components/schemas/agent" + } + } + } + } } } }, "404": { - "description": "Inbox not found" + "description": "Inbox not found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/bad_request_error" + } + } + } }, "403": { - "description": "Access denied" + "description": "Access denied", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/bad_request_error" + } + } + } } } } @@ -4096,7 +5351,7 @@ "/api/v1/accounts/{account_id}/inbox_members": { "parameters": [ { - "$ref": "#/parameters/account_id" + "$ref": "#/components/parameters/account_id" } ], "post": { @@ -4108,57 +5363,89 @@ "description": "Add a new Agent to Inbox", "security": [ { - "userApiKey": [ - - ] + "userApiKey": [] } ], - "parameters": [ - { - "name": "data", - "in": "body", - "required": true, - "schema": { - "type": "object", - "required": [ - "inbox_id", - "user_ids" - ], - "properties": { - "inbox_id": { - "type": "string", - "description": "The ID of the inbox" - }, - "user_ids": { - "type": "array", - "items": { - "type": "integer" + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "inbox_id", + "user_ids" + ], + "properties": { + "inbox_id": { + "type": "integer", + "description": "The ID of the inbox", + "example": 1 }, - "description": "IDs of users to be added to the inbox" + "user_ids": { + "type": "array", + "items": { + "type": "integer" + }, + "description": "IDs of users to be added to the inbox", + "example": [ + 1 + ] + } } } } } - ], + }, "responses": { "200": { "description": "Success", - "schema": { - "type": "array", - "description": "Array of all active agents", - "items": { - "$ref": "#/definitions/agent" + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "payload": { + "type": "array", + "description": "Array of all active agents", + "items": { + "$ref": "#/components/schemas/agent" + } + } + } + } } } }, "404": { - "description": "Inbox not found" + "description": "Inbox not found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/bad_request_error" + } + } + } }, "403": { - "description": "Access denied" + "description": "Access denied", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/bad_request_error" + } + } + } }, "422": { - "description": "User must exist" + "description": "User must exist", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/bad_request_error" + } + } + } } } }, @@ -4171,57 +5458,89 @@ "description": "All agents except the one passed in params will be removed", "security": [ { - "userApiKey": [ - - ] + "userApiKey": [] } ], - "parameters": [ - { - "name": "data", - "in": "body", - "required": true, - "schema": { - "type": "object", - "required": [ - "inbox_id", - "user_ids" - ], - "properties": { - "inbox_id": { - "type": "string", - "description": "The ID of the inbox" - }, - "user_ids": { - "type": "array", - "items": { - "type": "integer" + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "inbox_id", + "user_ids" + ], + "properties": { + "inbox_id": { + "type": "string", + "description": "The ID of the inbox", + "example": 1 }, - "description": "IDs of users to be added to the inbox" + "user_ids": { + "type": "array", + "items": { + "type": "integer" + }, + "description": "IDs of users to be added to the inbox", + "example": [ + 1 + ] + } } } } } - ], + }, "responses": { "200": { "description": "Success", - "schema": { - "type": "array", - "description": "Array of all active agents", - "items": { - "$ref": "#/definitions/agent" + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "payload": { + "type": "array", + "description": "Array of all active agents", + "items": { + "$ref": "#/components/schemas/agent" + } + } + } + } } } }, "404": { - "description": "Inbox not found" + "description": "Inbox not found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/bad_request_error" + } + } + } }, "403": { - "description": "Access denied" + "description": "Access denied", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/bad_request_error" + } + } + } }, "422": { - "description": "User must exist" + "description": "User must exist", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/bad_request_error" + } + } + } } } }, @@ -4234,50 +5553,69 @@ "description": "Remove an Agent from Inbox", "security": [ { - "userApiKey": [ - - ] + "userApiKey": [] } ], - "parameters": [ - { - "name": "data", - "in": "body", - "required": true, - "schema": { - "type": "object", - "required": [ - "inbox_id", - "user_ids" - ], - "properties": { - "inbox_id": { - "type": "string", - "description": "The ID of the inbox" - }, - "user_ids": { - "type": "array", - "items": { - "type": "integer" + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "inbox_id", + "user_ids" + ], + "properties": { + "inbox_id": { + "type": "string", + "description": "The ID of the inbox" }, - "description": "IDs of users to be deleted from the inbox" + "user_ids": { + "type": "array", + "items": { + "type": "integer" + }, + "description": "IDs of users to be deleted from the inbox" + } } } } } - ], + }, "responses": { "200": { "description": "Success" }, "404": { - "description": "Inbox not found" + "description": "Inbox not found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/bad_request_error" + } + } + } }, "403": { - "description": "Access denied" + "description": "Access denied", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/bad_request_error" + } + } + } }, "422": { - "description": "User must exist" + "description": "User must exist", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/bad_request_error" + } + } + } } } } @@ -4285,10 +5623,10 @@ "/api/v1/accounts/{account_id}/conversations/{conversation_id}/messages": { "parameters": [ { - "$ref": "#/parameters/account_id" + "$ref": "#/components/parameters/account_id" }, { - "$ref": "#/parameters/conversation_id" + "$ref": "#/components/parameters/conversation_id" } ], "get": { @@ -4297,30 +5635,79 @@ ], "operationId": "list-all-messages", "summary": "Get messages", + "security": [ + { + "userApiKey": [] + } + ], "description": "List all messages of a conversation", "responses": { "200": { "description": "Success", - "schema": { - "type": "array", - "description": "Array of messages", - "items": { - "allOf": [ - { - "$ref": "#/definitions/generic_id" - }, - { - "$ref": "#/definitions/message" + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "meta": { + "type": "object", + "properties": { + "labels": { + "type": "array", + "items": { + "type": "string" + } + }, + "additional_attributes": { + "type": "object" + }, + "contact": { + "$ref": "#/components/schemas/contact" + }, + "assignee": { + "$ref": "#/components/schemas/agent" + }, + "agent_last_seen_at": { + "type": "string", + "format": "date-time" + }, + "assignee_last_seen_at": { + "type": "string", + "format": "date-time" + } + } + }, + "payload": { + "type": "array", + "description": "Array of messages", + "items": { + "$ref": "#/components/schemas/message" + } + } } - ] + } } } }, "404": { - "description": "Conversation not found" + "description": "Conversation not found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/bad_request_error" + } + } + } }, "401": { - "description": "Unauthorized" + "description": "Unauthorized", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/bad_request_error" + } + } + } } } }, @@ -4333,45 +5720,59 @@ "description": "Create a new message in the conversation", "security": [ { - "userApiKey": [ - - ] + "userApiKey": [] }, { - "agentBotApiKey": [ - - ] + "agentBotApiKey": [] } ], - "parameters": [ - { - "name": "data", - "in": "body", - "required": true, - "schema": { - "$ref": "#/definitions/conversation_message_create" + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/conversation_message_create_payload" + } } } - ], + }, "responses": { "200": { "description": "Success", - "schema": { - "allOf": [ - { - "$ref": "#/definitions/generic_id" - }, - { - "$ref": "#/definitions/message" + "content": { + "application/json": { + "schema": { + "allOf": [ + { + "$ref": "#/components/schemas/generic_id" + }, + { + "$ref": "#/components/schemas/message" + } + ] } - ] + } } }, "404": { - "description": "Conversation not found" + "description": "Conversation not found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/bad_request_error" + } + } + } }, "403": { - "description": "Access denied" + "description": "Access denied", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/bad_request_error" + } + } + } } } } @@ -4379,13 +5780,13 @@ "/api/v1/accounts/{account_id}/conversations/{conversation_id}/messages/{message_id}": { "parameters": [ { - "$ref": "#/parameters/account_id" + "$ref": "#/components/parameters/account_id" }, { - "$ref": "#/parameters/conversation_id" + "$ref": "#/components/parameters/conversation_id" }, { - "$ref": "#/parameters/message_id" + "$ref": "#/components/parameters/message_id" } ], "delete": { @@ -4394,16 +5795,35 @@ ], "operationId": "delete-a-message", "summary": "Delete a message", + "security": [ + { + "userApiKey": [] + } + ], "description": "Delete a message and it's attachments from the conversation.", "responses": { "200": { "description": "Success" }, "401": { - "description": "Unauthorized" + "description": "Unauthorized", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/bad_request_error" + } + } + } }, "404": { - "description": "The message or conversation does not exist in the account" + "description": "The message or conversation does not exist in the account", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/bad_request_error" + } + } + } } } } @@ -4411,7 +5831,7 @@ "/api/v1/accounts/{account_id}/integrations/apps": { "parameters": [ { - "$ref": "#/parameters/account_id" + "$ref": "#/components/parameters/account_id" } ], "get": { @@ -4420,23 +5840,51 @@ ], "operationId": "get-details-of-all-integrations", "summary": "List all the Integrations", + "security": [ + { + "userApiKey": [] + } + ], "description": "Get the details of all Integrations available for the account", "responses": { "200": { "description": "Success", - "schema": { - "type": "array", - "description": "Array of Integration apps", - "items": { - "$ref": "#/definitions/integrations_app" + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "payload": { + "type": "array", + "description": "Array of Integration apps", + "items": { + "$ref": "#/components/schemas/integrations_app" + } + } + } + } } } }, "401": { - "description": "Unauthorized" + "description": "Unauthorized", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/bad_request_error" + } + } + } }, "404": { - "description": "Url not found" + "description": "Url not found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/bad_request_error" + } + } + } } } } @@ -4449,28 +5897,46 @@ "operationId": "create-an-integration-hook", "summary": "Create an integration hook", "description": "Create an integration hook", - "parameters": [ + "security": [ { - "$ref": "#/parameters/account_id" - }, - { - "name": "data", - "in": "body", - "required": true, - "schema": { - "$ref": "#/definitions/integrations_hook_create_payload" - } + "userApiKey": [] } ], + "parameters": [ + { + "$ref": "#/components/parameters/account_id" + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/integrations_hook_create_payload" + } + } + } + }, "responses": { "200": { "description": "Success", - "schema": { - "$ref": "#/definitions/integrations_hook" + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/integrations_hook" + } + } } }, "401": { - "description": "Unauthorized" + "description": "Unauthorized", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/bad_request_error" + } + } + } } } } @@ -4483,31 +5949,49 @@ "operationId": "update-an-integrations-hook", "summary": "Update an Integration Hook", "description": "Update an Integration Hook", - "parameters": [ + "security": [ { - "$ref": "#/parameters/account_id" - }, - { - "$ref": "#/parameters/hook_id" - }, - { - "name": "data", - "in": "body", - "required": true, - "schema": { - "$ref": "#/definitions/integrations_hook_update_payload" - } + "userApiKey": [] } ], + "parameters": [ + { + "$ref": "#/components/parameters/account_id" + }, + { + "$ref": "#/components/parameters/hook_id" + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/integrations_hook_update_payload" + } + } + } + }, "responses": { "200": { "description": "Success", - "schema": { - "$ref": "#/definitions/integrations_hook" + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/integrations_hook" + } + } } }, "401": { - "description": "Unauthorized" + "description": "Unauthorized", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/bad_request_error" + } + } + } } } }, @@ -4518,12 +6002,17 @@ "operationId": "delete-an-integration-hook", "summary": "Delete an Integration Hook", "description": "Delete an Integration Hook", + "security": [ + { + "userApiKey": [] + } + ], "parameters": [ { - "$ref": "#/parameters/account_id" + "$ref": "#/components/parameters/account_id" }, { - "$ref": "#/parameters/hook_id" + "$ref": "#/components/parameters/hook_id" } ], "responses": { @@ -4531,10 +6020,24 @@ "description": "Success" }, "401": { - "description": "Unauthorized" + "description": "Unauthorized", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/bad_request_error" + } + } + } }, "404": { - "description": "The hook does not exist in the account" + "description": "The hook does not exist in the account", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/bad_request_error" + } + } + } } } } @@ -4547,15 +6050,31 @@ "operationId": "fetchProfile", "summary": "Fetch user profile", "description": "Get the user profile details", + "security": [ + { + "userApiKey": [] + } + ], "responses": { "200": { "description": "Success", - "schema": { - "$ref": "#/definitions/user" + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/user" + } + } } }, "401": { - "description": "Unauthorized" + "description": "Unauthorized", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/bad_request_error" + } + } + } } } } @@ -4563,7 +6082,7 @@ "/api/v1/accounts/{account_id}/teams": { "parameters": [ { - "$ref": "#/parameters/account_id" + "$ref": "#/components/parameters/account_id" } ], "get": { @@ -4572,20 +6091,36 @@ ], "operationId": "list-all-teams", "summary": "List all teams", + "security": [ + { + "userApiKey": [] + } + ], "description": "List all teams available in the current account", "responses": { "200": { "description": "Success", - "schema": { - "type": "array", - "description": "Array of teams", - "items": { - "$ref": "#/definitions/team" + "content": { + "application/json": { + "schema": { + "type": "array", + "description": "Array of teams", + "items": { + "$ref": "#/components/schemas/team" + } + } } } }, "401": { - "description": "Unauthorized" + "description": "Unauthorized", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/bad_request_error" + } + } + } } } }, @@ -4595,29 +6130,47 @@ ], "operationId": "create-a-team", "summary": "Create a team", + "security": [ + { + "userApiKey": [] + } + ], "description": "Create a team in the account", "parameters": [ { - "$ref": "#/parameters/account_id" - }, - { - "name": "data", - "in": "body", - "required": true, - "schema": { - "$ref": "#/definitions/team_create_update_payload" - } + "$ref": "#/components/parameters/account_id" } ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/team_create_update_payload" + } + } + } + }, "responses": { "200": { "description": "Success", - "schema": { - "$ref": "#/definitions/team" + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/team" + } + } } }, "401": { - "description": "Unauthorized" + "description": "Unauthorized", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/bad_request_error" + } + } + } } } } @@ -4625,10 +6178,10 @@ "/api/v1/accounts/{account_id}/teams/{team_id}": { "parameters": [ { - "$ref": "#/parameters/account_id" + "$ref": "#/components/parameters/account_id" }, { - "$ref": "#/parameters/team_id" + "$ref": "#/components/parameters/team_id" } ], "get": { @@ -4637,19 +6190,42 @@ ], "operationId": "get-details-of-a-single-team", "summary": "Get a team details", + "security": [ + { + "userApiKey": [] + } + ], "description": "Get the details of a team in the account", "responses": { "200": { "description": "Success", - "schema": { - "$ref": "#/definitions/team" + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/team" + } + } } }, "401": { - "description": "Unauthorized" + "description": "Unauthorized", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/bad_request_error" + } + } + } }, "404": { - "description": "The given team ID does not exist in the account" + "description": "The given team ID does not exist in the account", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/bad_request_error" + } + } + } } } }, @@ -4659,26 +6235,42 @@ ], "operationId": "update-a-team", "summary": "Update a team", - "description": "Update a team's attributes", - "parameters": [ + "security": [ { - "name": "data", - "in": "body", - "required": true, - "schema": { - "$ref": "#/definitions/team_create_update_payload" - } + "userApiKey": [] } ], + "description": "Update a team's attributes", + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/team_create_update_payload" + } + } + } + }, "responses": { "200": { "description": "Success", - "schema": { - "$ref": "#/definitions/team" + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/team" + } + } } }, "401": { - "description": "Unauthorized" + "description": "Unauthorized", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/bad_request_error" + } + } + } } } }, @@ -4688,16 +6280,35 @@ ], "operationId": "delete-a-team", "summary": "Delete a team", + "security": [ + { + "userApiKey": [] + } + ], "description": "Delete a team from the account", "responses": { "200": { "description": "Success" }, "401": { - "description": "Unauthorized" + "description": "Unauthorized", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/bad_request_error" + } + } + } }, "404": { - "description": "The team does not exist in the account" + "description": "The team does not exist in the account", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/bad_request_error" + } + } + } } } } @@ -4705,10 +6316,10 @@ "/api/v1/accounts/{account_id}/teams/{team_id}/team_members": { "parameters": [ { - "$ref": "#/parameters/account_id" + "$ref": "#/components/parameters/account_id" }, { - "$ref": "#/parameters/team_id" + "$ref": "#/components/parameters/team_id" } ], "get": { @@ -4720,35 +6331,51 @@ "description": "Get Details of Agents in an Team", "security": [ { - "userApiKey": [ - - ] + "userApiKey": [] } ], "parameters": [ { - "$ref": "#/parameters/account_id" + "$ref": "#/components/parameters/account_id" }, { - "$ref": "#/parameters/team_id" + "$ref": "#/components/parameters/team_id" } ], "responses": { "200": { "description": "Success", - "schema": { - "type": "array", - "description": "Array of all agents in the team", - "items": { - "$ref": "#/definitions/agent" + "content": { + "application/json": { + "schema": { + "type": "array", + "description": "Array of all agents in the team", + "items": { + "$ref": "#/components/schemas/agent" + } + } + } + } + }, + "403": { + "description": "Access denied", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/bad_request_error" + } } } }, "404": { - "description": "Inbox not found" - }, - "403": { - "description": "Access denied" + "description": "Team not found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/bad_request_error" + } + } + } } } }, @@ -4761,52 +6388,78 @@ "description": "Add a new Agent to Team", "security": [ { - "userApiKey": [ - - ] + "userApiKey": [] } ], - "parameters": [ - { - "name": "data", - "in": "body", - "required": true, - "schema": { - "type": "object", - "required": [ - "user_ids" - ], - "properties": { - "user_ids": { - "type": "array", - "items": { - "type": "integer" - }, - "description": "IDs of users to be added to the team" + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "user_ids" + ], + "properties": { + "user_ids": { + "type": "array", + "items": { + "type": "integer" + }, + "description": "IDs of users to be added to the team", + "example": [ + 1 + ] + } } } } } - ], + }, "responses": { "200": { "description": "Success", - "schema": { - "type": "array", - "description": "Array of all active agents", - "items": { - "$ref": "#/definitions/agent" + "content": { + "application/json": { + "schema": { + "type": "array", + "description": "Array of all active agents", + "items": { + "$ref": "#/components/schemas/agent" + } + } + } + } + }, + "403": { + "description": "Access denied", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/bad_request_error" + } } } }, "404": { - "description": "Team not found" - }, - "403": { - "description": "Access denied" + "description": "Team not found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/bad_request_error" + } + } + } }, "422": { - "description": "User must exist" + "description": "User must exist", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/bad_request_error" + } + } + } } } }, @@ -4819,52 +6472,78 @@ "description": "All agents except the one passed in params will be removed", "security": [ { - "userApiKey": [ - - ] + "userApiKey": [] } ], - "parameters": [ - { - "name": "data", - "in": "body", - "required": true, - "schema": { - "type": "object", - "required": [ - "user_ids" - ], - "properties": { - "user_ids": { - "type": "array", - "items": { - "type": "integer" - }, - "description": "IDs of users to be added to the team" + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "user_ids" + ], + "properties": { + "user_ids": { + "type": "array", + "items": { + "type": "integer" + }, + "description": "IDs of users to be added to the team", + "example": [ + 1 + ] + } } } } } - ], + }, "responses": { "200": { "description": "Success", - "schema": { - "type": "array", - "description": "Array of all agents in the team", - "items": { - "$ref": "#/definitions/agent" + "content": { + "application/json": { + "schema": { + "type": "array", + "description": "Array of all agents in the team", + "items": { + "$ref": "#/components/schemas/agent" + } + } + } + } + }, + "403": { + "description": "Access denied", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/bad_request_error" + } } } }, "404": { - "description": "Team not found" - }, - "403": { - "description": "Access denied" + "description": "Team not found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/bad_request_error" + } + } + } }, "422": { - "description": "User must exist" + "description": "User must exist", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/bad_request_error" + } + } + } } } }, @@ -4877,46 +6556,64 @@ "description": "Remove an Agent from Team", "security": [ { - "userApiKey": [ - - ] + "userApiKey": [] } ], - "parameters": [ - { - "name": "data", - "in": "body", - "required": true, - "schema": { - "type": "object", - "required": [ - "team_id", - "user_ids" - ], - "properties": { - "user_ids": { - "type": "array", - "items": { - "type": "integer" - }, - "description": "IDs of users to be deleted from the team" + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "user_ids" + ], + "properties": { + "user_ids": { + "type": "array", + "items": { + "type": "integer" + }, + "description": "IDs of users to be deleted from the team" + } } } } } - ], + }, "responses": { "200": { "description": "Success" }, - "404": { - "description": "Team not found" - }, "403": { - "description": "Access denied" + "description": "Access denied", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/bad_request_error" + } + } + } + }, + "404": { + "description": "Team not found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/bad_request_error" + } + } + } }, "422": { - "description": "User must exist" + "description": "User must exist", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/bad_request_error" + } + } + } } } } @@ -4924,17 +6621,19 @@ "/api/v1/accounts/{account_id}/custom_filters": { "parameters": [ { - "$ref": "#/parameters/account_id" + "$ref": "#/components/parameters/account_id" }, { "in": "query", "name": "filter_type", - "type": "string", - "enum": [ - "conversation", - "contact", - "report" - ], + "schema": { + "type": "string", + "enum": [ + "conversation", + "contact", + "report" + ] + }, "required": false, "description": "The type of custom filter" } @@ -4946,19 +6645,35 @@ "operationId": "list-all-filters", "summary": "List all custom filters", "description": "List all custom filters in a category of a user", + "security": [ + { + "userApiKey": [] + } + ], "responses": { "200": { "description": "Success", - "schema": { - "type": "array", - "description": "Array of custom filters", - "items": { - "$ref": "#/definitions/custom_filter" + "content": { + "application/json": { + "schema": { + "type": "array", + "description": "Array of custom filters", + "items": { + "$ref": "#/components/schemas/custom_filter" + } + } } } }, "401": { - "description": "Unauthorized" + "description": "Unauthorized", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/bad_request_error" + } + } + } } } }, @@ -4971,26 +6686,44 @@ "description": "Create a custom filter in the account", "parameters": [ { - "$ref": "#/parameters/account_id" - }, - { - "name": "data", - "in": "body", - "required": true, - "schema": { - "$ref": "#/definitions/custom_filter_create_update_payload" - } + "$ref": "#/components/parameters/account_id" } ], + "security": [ + { + "userApiKey": [] + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/custom_filter_create_update_payload" + } + } + } + }, "responses": { "200": { "description": "Success", - "schema": { - "$ref": "#/definitions/custom_filter" + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/custom_filter" + } + } } }, "401": { - "description": "Unauthorized" + "description": "Unauthorized", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/bad_request_error" + } + } + } } } } @@ -4998,10 +6731,10 @@ "/api/v1/accounts/{account_id}/custom_filters/{custom_filter_id}": { "parameters": [ { - "$ref": "#/parameters/account_id" + "$ref": "#/components/parameters/account_id" }, { - "$ref": "#/parameters/custom_filter_id" + "$ref": "#/components/parameters/custom_filter_id" } ], "get": { @@ -5011,18 +6744,41 @@ "operationId": "get-details-of-a-single-custom-filter", "summary": "Get a custom filter details", "description": "Get the details of a custom filter in the account", + "security": [ + { + "userApiKey": [] + } + ], "responses": { "200": { "description": "Success", - "schema": { - "$ref": "#/definitions/custom_filter" + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/custom_filter" + } + } } }, "401": { - "description": "Unauthorized" + "description": "Unauthorized", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/bad_request_error" + } + } + } }, "404": { - "description": "The given team ID does not exist in the account" + "description": "The given team ID does not exist in the account", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/bad_request_error" + } + } + } } } }, @@ -5032,26 +6788,42 @@ ], "operationId": "update-a-custom-filter", "summary": "Update a custom filter", - "description": "Update a custom filter's attributes", - "parameters": [ + "security": [ { - "name": "data", - "in": "body", - "required": true, - "schema": { - "$ref": "#/definitions/custom_filter_create_update_payload" - } + "userApiKey": [] } ], + "description": "Update a custom filter's attributes", + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/custom_filter_create_update_payload" + } + } + } + }, "responses": { "200": { "description": "Success", - "schema": { - "$ref": "#/definitions/custom_filter" + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/custom_filter" + } + } } }, "401": { - "description": "Unauthorized" + "description": "Unauthorized", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/bad_request_error" + } + } + } } } }, @@ -5061,16 +6833,35 @@ ], "operationId": "delete-a-custom-filter", "summary": "Delete a custom filter", + "security": [ + { + "userApiKey": [] + } + ], "description": "Delete a custom filter from the account", "responses": { "200": { "description": "Success" }, "401": { - "description": "Unauthorized" + "description": "Unauthorized", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/bad_request_error" + } + } + } }, "404": { - "description": "The custom filter does not exist in the account" + "description": "The custom filter does not exist in the account", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/bad_request_error" + } + } + } } } } @@ -5078,7 +6869,7 @@ "/api/v1/accounts/{account_id}/webhooks": { "parameters": [ { - "$ref": "#/parameters/account_id" + "$ref": "#/components/parameters/account_id" } ], "get": { @@ -5087,20 +6878,36 @@ ], "operationId": "list-all-webhooks", "summary": "List all webhooks", + "security": [ + { + "userApiKey": [] + } + ], "description": "List all webhooks in the account", "responses": { "200": { "description": "Success", - "schema": { - "type": "array", - "description": "Array of webhook objects", - "items": { - "$ref": "#/definitions/webhook" + "content": { + "application/json": { + "schema": { + "type": "array", + "description": "Array of webhook objects", + "items": { + "$ref": "#/components/schemas/webhook" + } + } } } }, "401": { - "description": "Unauthorized" + "description": "Unauthorized", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/bad_request_error" + } + } + } } } }, @@ -5110,29 +6917,47 @@ ], "operationId": "create-a-webhook", "summary": "Add a webhook", + "security": [ + { + "userApiKey": [] + } + ], "description": "Add a webhook subscription to the account", "parameters": [ { - "$ref": "#/parameters/account_id" - }, - { - "name": "data", - "in": "body", - "required": true, - "schema": { - "$ref": "#/definitions/webhook_create_update_payload" - } + "$ref": "#/components/parameters/account_id" } ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/webhook_create_update_payload" + } + } + } + }, "responses": { "200": { "description": "Success", - "schema": { - "$ref": "#/definitions/webhook" + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/webhook" + } + } } }, "401": { - "description": "Unauthorized" + "description": "Unauthorized", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/bad_request_error" + } + } + } } } } @@ -5140,10 +6965,10 @@ "/api/v1/accounts/{account_id}/webhooks/{webhook_id}": { "parameters": [ { - "$ref": "#/parameters/account_id" + "$ref": "#/components/parameters/account_id" }, { - "$ref": "#/parameters/webhook_id" + "$ref": "#/components/parameters/webhook_id" } ], "patch": { @@ -5152,29 +6977,47 @@ ], "operationId": "update-a-webhook", "summary": "Update a webhook object", + "security": [ + { + "userApiKey": [] + } + ], "description": "Update a webhook object in the account", "parameters": [ { - "$ref": "#/parameters/account_id" - }, - { - "name": "data", - "in": "body", - "required": true, - "schema": { - "$ref": "#/definitions/webhook_create_update_payload" - } + "$ref": "#/components/parameters/account_id" } ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/webhook_create_update_payload" + } + } + } + }, "responses": { "200": { "description": "Success", - "schema": { - "$ref": "#/definitions/webhook" + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/webhook" + } + } } }, "401": { - "description": "Unauthorized" + "description": "Unauthorized", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/bad_request_error" + } + } + } } } }, @@ -5184,16 +7027,35 @@ ], "operationId": "delete-a-webhook", "summary": "Delete a webhook", + "security": [ + { + "userApiKey": [] + } + ], "description": "Delete a webhook from the account", "responses": { "200": { "description": "Success" }, "401": { - "description": "Unauthorized" + "description": "Unauthorized", + "content": { + "application/json": { + "schema": { + "type": "object" + } + } + } }, "404": { - "description": "The webhook does not exist in the account" + "description": "The webhook does not exist in the account", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/bad_request_error" + } + } + } } } } @@ -5201,30 +7063,36 @@ "/api/v2/accounts/{account_id}/reports": { "parameters": [ { - "$ref": "#/parameters/account_id" + "$ref": "#/components/parameters/account_id" }, { - "$ref": "#/parameters/report_metric" + "$ref": "#/components/parameters/report_metric" }, { - "$ref": "#/parameters/report_type" + "$ref": "#/components/parameters/report_type" }, { "in": "query", "name": "id", - "type": "string", + "schema": { + "type": "string" + }, "description": "The Id of specific object in case of agent/inbox/label" }, { "in": "query", "name": "since", - "type": "string", + "schema": { + "type": "string" + }, "description": "The timestamp from where report should start." }, { "in": "query", "name": "until", - "type": "string", + "schema": { + "type": "string" + }, "description": "The timestamp from where report should stop." } ], @@ -5234,31 +7102,54 @@ ], "operationId": "list-all-conversation-statistics", "summary": "Get Account reports", + "security": [ + { + "userApiKey": [] + } + ], "description": "Get Account reports for a specific type, metric and date range", "responses": { "200": { "description": "Success", - "schema": { - "type": "array", - "description": "Array of date based conversation statistics", - "items": { - "type": "object", - "properties": { - "value": { - "type": "string" - }, - "timestamp": { - "type": "number" + "content": { + "application/json": { + "schema": { + "type": "array", + "description": "Array of date based conversation statistics", + "items": { + "type": "object", + "properties": { + "value": { + "type": "string" + }, + "timestamp": { + "type": "number" + } + } } } } } }, "404": { - "description": "reports not found" + "description": "reports not found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/bad_request_error" + } + } + } }, "403": { - "description": "Access denied" + "description": "Access denied", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/bad_request_error" + } + } + } } } } @@ -5266,27 +7157,33 @@ "/api/v2/accounts/{account_id}/reports/summary": { "parameters": [ { - "$ref": "#/parameters/account_id" + "$ref": "#/components/parameters/account_id" }, { - "$ref": "#/parameters/report_type" + "$ref": "#/components/parameters/report_type" }, { "in": "query", "name": "id", - "type": "string", + "schema": { + "type": "string" + }, "description": "The Id of specific object in case of agent/inbox/label" }, { "in": "query", "name": "since", - "type": "string", + "schema": { + "type": "string" + }, "description": "The timestamp from where report should start." }, { "in": "query", "name": "until", - "type": "string", + "schema": { + "type": "string" + }, "description": "The timestamp from where report should stop." } ], @@ -5296,19 +7193,42 @@ ], "operationId": "list-all-conversation-statistics-summary", "summary": "Get Account reports summary", + "security": [ + { + "userApiKey": [] + } + ], "description": "Get Account reports summary for a specific type and date range", "responses": { "200": { "description": "Success", - "schema": { - "$ref": "#/definitions/account_summary" + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/account_summary" + } + } } }, "404": { - "description": "reports not found" + "description": "reports not found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/bad_request_error" + } + } + } }, "403": { - "description": "Access denied" + "description": "Access denied", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/bad_request_error" + } + } + } } } } @@ -5316,15 +7236,17 @@ "/api/v2/accounts/{account_id}/reports/conversations": { "parameters": [ { - "$ref": "#/parameters/account_id" + "$ref": "#/components/parameters/account_id" }, { "in": "query", "name": "type", - "type": "string", - "enum": [ - "account" - ], + "schema": { + "type": "string", + "enum": [ + "account" + ] + }, "required": true, "description": "Type of report" } @@ -5335,31 +7257,54 @@ ], "operationId": "get-account-conversation-metrics", "summary": "Account Conversation Metrics", + "security": [ + { + "userApiKey": [] + } + ], "description": "Get conversation metrics for Account", "responses": { "200": { "description": "Success", - "schema": { - "type": "object", - "description": "Object of account conversation metrics", - "properties": { - "open": { - "type": "number" - }, - "unattended": { - "type": "number" - }, - "unassigned": { - "type": "number" + "content": { + "application/json": { + "schema": { + "type": "object", + "description": "Object of account conversation metrics", + "properties": { + "open": { + "type": "number" + }, + "unattended": { + "type": "number" + }, + "unassigned": { + "type": "number" + } + } } } } }, "404": { - "description": "reports not found" + "description": "reports not found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/bad_request_error" + } + } + } }, "403": { - "description": "Access denied" + "description": "Access denied", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/bad_request_error" + } + } + } } } } @@ -5367,22 +7312,26 @@ "/api/v2/accounts/{account_id}/reports/conversations/": { "parameters": [ { - "$ref": "#/parameters/account_id" + "$ref": "#/components/parameters/account_id" }, { "in": "query", "name": "type", - "type": "string", - "enum": [ - "agent" - ], + "schema": { + "type": "string", + "enum": [ + "agent" + ] + }, "required": true, "description": "Type of report" }, { "in": "query", "name": "user_id", - "type": "string", + "schema": { + "type": "string" + }, "description": "The numeric ID of the user" } ], @@ -5392,376 +7341,43 @@ ], "operationId": "get-agent-conversation-metrics", "summary": "Agent Conversation Metrics", + "security": [ + { + "userApiKey": [] + } + ], "description": "Get conversation metrics for Agent", "responses": { "200": { "description": "Success", - "schema": { - "type": "array", - "description": "Array of agent based conversation metrics", - "items": { - "$ref": "#/definitions/agent_conversation_metrics" + "content": { + "application/json": { + "schema": { + "type": "array", + "description": "Array of agent based conversation metrics", + "items": { + "$ref": "#/components/schemas/agent_conversation_metrics" + } + } } } }, "404": { - "description": "reports not found" + "description": "reports not found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/bad_request_error" + } + } + } }, "403": { - "description": "Access denied" - } - } - } - } - }, - "definitions": { - "bad_request_error": { - "title": "data", - "type": "object", - "properties": { - "description": { - "type": "string" - }, - "errors": { - "type": "array", - "items": { - "$ref": "#/definitions/request_error" - } - } - } - }, - "request_error": { - "type": "object", - "properties": { - "field": { - "type": "string" - }, - "message": { - "type": "string" - }, - "code": { - "type": "string" - } - } - }, - "generic_id": { - "type": "object", - "properties": { - "id": { - "type": "number" - } - } - }, - "canned_response": { - "type": "object", - "properties": { - "id": { - "type": "integer", - "description": "ID of the canned response" - }, - "content": { - "type": "string", - "description": "Message content for canned response" - }, - "short_code": { - "type": "string", - "description": "Short Code for quick access of the canned response" - }, - "account_id": { - "type": "integer", - "description": "Account Id" - } - } - }, - "custom_attribute": { - "type": "object", - "properties": { - "id": { - "type": "integer", - "description": "Identifier" - }, - "attribute_display_name": { - "type": "string", - "description": "Attribute display name" - }, - "attribute_display_type": { - "type": "string", - "description": "Attribute display type (text, number, currency, percent, link, date, list, checkbox)" - }, - "attribute_description": { - "type": "string", - "description": "Attribute description" - }, - "attribute_key": { - "type": "string", - "description": "Attribute unique key value" - }, - "attribute_values": { - "type": "string", - "description": "Attribute values" - }, - "default_value": { - "type": "string", - "description": "Attribute default value" - }, - "attribute_model": { - "type": "string", - "description": "Attribute type(conversation_attribute/contact_attribute)" - }, - "account_id": { - "type": "integer", - "description": "Account Id" - } - } - }, - "automation_rule": { - "type": "object", - "properties": { - "event_name": { - "type": "string", - "description": "Automation Rule event, on which we call the actions(conversation_created, conversation_updated, message_created)", - "enum": [ - "conversation_created", - "conversation_updated", - "message_created" - ], - "example": "message_created" - }, - "name": { - "type": "string", - "description": "The name of the rule", - "example": "Add label on message create event" - }, - "description": { - "type": "string", - "description": "Description to give more context about the rule", - "example": "Add label support and sales on message create event if incoming message content contains text help" - }, - "active": { - "type": "boolean", - "description": "Enable/disable automation rule" - }, - "actions": { - "type": "array", - "description": "Array of actions which we perform when condition matches", - "items": { - "type": "object", - "example": { - "action_name": "add_label", - "action_params": [ - "support", - "sales" - ] - } - } - }, - "conditions": { - "type": "array", - "description": "Array of conditions on which conversation/message filter would work", - "items": { - "type": "object", - "example": { - "attribute_key": "content", - "filter_operator": "contains", - "values": [ - "help" - ], - "query_operator": "nil" - } - } - }, - "account_id": { - "type": "integer", - "description": "Account Id" - } - } - }, - "portal": { - "type": "object", - "properties": { - "id": { - "type": "integer" - }, - "archived": { - "type": "boolean" - }, - "color": { - "type": "string" - }, - "config": { - "type": "object", - "description": "Save information about locales, allowed_locales and default portal/help-center locale" - }, - "custom_domain": { - "type": "string" - }, - "header_text": { - "type": "string", - "description": "The text content." - }, - "homepage_link": { - "type": "string" - }, - "name": { - "type": "string" - }, - "slug": { - "type": "string" - }, - "page_title": { - "type": "string" - }, - "account_id": { - "type": "integer" - }, - "categories": { - "type": "array", - "items": { - "$ref": "#/definitions/category" - } - }, - "articles": { - "type": "array", - "items": { - "$ref": "#/definitions/article" - } - } - } - }, - "category": { - "type": "object", - "properties": { - "id": { - "type": "integer" - }, - "description": { - "type": "string", - "description": "The text content." - }, - "locale": { - "type": "string" - }, - "name": { - "type": "string" - }, - "slug": { - "type": "string" - }, - "position": { - "type": "integer" - }, - "portal_id": { - "type": "integer" - }, - "account_id": { - "type": "integer" - }, - "associated_category_id": { - "type": "integer", - "description": "To associate similar categories to each other, e.g same category of product documentation in different languages" - }, - "parent_category_id": { - "type": "integer", - "description": "To define parent category, e.g product documentation has multiple level features in sales category or in engineering category." - } - } - }, - "article": { - "type": "object", - "properties": { - "id": { - "type": "integer" - }, - "content": { - "type": "string", - "description": "The text content." - }, - "meta": { - "type": "object" - }, - "position": { - "type": "integer" - }, - "status": { - "type": "integer", - "enum": [ - "draft", - "published", - "archived" - ] - }, - "title": { - "type": "string" - }, - "slug": { - "type": "string" - }, - "views": { - "type": "integer" - }, - "portal_id": { - "type": "integer" - }, - "account_id": { - "type": "integer" - }, - "author_id": { - "type": "integer" - }, - "category_id": { - "type": "integer" - }, - "folder_id": { - "type": "integer" - }, - "associated_article_id": { - "type": "integer", - "description": "To associate similar articles to each other, e.g to provide the link for the reference." - } - } - }, - "contact": { - "type": "object", - "properties": { - "payload": { - "type": "object", - "properties": { - "contact": { - "type": "object", - "properties": { - "email": { - "type": "string", - "description": "Email address of the contact" - }, - "name": { - "type": "string", - "description": "The name of the contact" - }, - "phone_number": { - "type": "string", - "description": "Phone number of the contact" - }, - "thumbnail": { - "type": "string", - "description": "Avatar URL of the contact" - }, - "additional_attributes": { - "type": "object", - "description": "The object containing additional attributes related to the contact" - }, - "custom_attributes": { - "type": "object", - "description": "The object to save custom attributes for contact, accepts custom attributes key and value", - "example": { - "attribute_key": "attribute_value", - "signed_up_at": "dd/mm/yyyy" - } - }, - "contact_inboxes": { - "type": "array", - "items": { - "$ref": "#/definitions/contact_inboxes" - } + "description": "Access denied", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/bad_request_error" } } } @@ -5769,1341 +7385,3251 @@ } } }, - "conversation": { - "type": "object", - "properties": { - "id": { - "type": "number", - "description": "ID of the conversation" + "/accounts/{account_id}/conversations/{conversation_id}/messages": { + "parameters": [ + { + "$ref": "#/components/parameters/account_id" }, - "messages": { - "type": "array", - "items": { - "$ref": "#/definitions/message" - } - }, - "account_id": { - "type": "number", - "description": "Account Id" - }, - "inbox_id": { - "type": "number", - "description": "ID of the inbox" - }, - "status": { - "type": "string", - "enum": [ - "open", - "resolved", - "pending" - ], - "description": "The status of the conversation" - }, - "timestamp": { - "type": "string", - "description": "The time at which conversation was created" - }, - "contact_last_seen_at": { - "type": "string" - }, - "agent_last_seen_at": { - "type": "string" - }, - "unread_count": { - "type": "number", - "description": "The number of unread messages" - }, - "additional_attributes": { - "type": "object", - "description": "The object containing additional attributes related to the conversation" - }, - "custom_attributes": { - "type": "object", - "description": "The object to save custom attributes for conversation, accepts custom attributes key and value", - "example": { - "attribute_key": "attribute_value", - "priority_conversation_number": 3 + { + "name": "conversation_id", + "in": "path", + "description": "ID of the conversation", + "required": true, + "schema": { + "type": "number" } } - } - }, - "message": { - "type": "object", - "properties": { - "content": { - "type": "string", - "description": "The text content of the message" - }, - "content_type": { - "type": "string", - "enum": [ - "text", - "input_select", - "cards", - "form" - ], - "description": "The type of the template message" - }, - "content_attributes": { - "type": "object", - "description": "The content attributes for each content_type" - }, - "message_type": { - "type": "string", - "enum": [ - "incoming", - "outgoing", - "activity", - "template" - ], - "description": "The type of the message" - }, - "created_at": { - "type": "integer", - "description": "The time at which message was created" - }, - "private": { - "type": "boolean", - "description": "The flags which shows whether the message is private or not" - }, - "attachment": { - "type": "object", - "description": "The file object attached to the image" - }, - "sender": { - "type": "object", - "description": "User/Agent/AgentBot object" - }, - "conversation_id": { - "type": "number", - "description": "ID of the conversation" - } - } - }, - "user": { - "type": "object", - "properties": { - "id": { - "type": "number" - }, - "uid": { - "type": "string" - }, - "name": { - "type": "string" - }, - "available_name": { - "type": "string" - }, - "display_name": { - "type": "string" - }, - "email": { - "type": "string" - }, - "account_id": { - "type": "number" - }, - "role": { - "type": "string", - "enum": [ - "agent", - "administrator" - ] - }, - "confirmed": { - "type": "boolean" - }, - "custom_attributes": { - "type": "object", - "description": "Available for users who are created through platform APIs and has custom attributes associated." - }, - "accounts": { - "type": "array", - "items": { - "$ref": "#/definitions/account" - } - } - } - }, - "agent": { - "type": "object", - "properties": { - "id": { - "type": "integer" - }, - "uid": { - "type": "string" - }, - "name": { - "type": "string" - }, - "available_name": { - "type": "string" - }, - "display_name": { - "type": "string" - }, - "email": { - "type": "string" - }, - "account_id": { - "type": "integer" - }, - "role": { - "type": "string", - "enum": [ - "agent", - "administrator" - ] - }, - "confirmed": { - "type": "boolean" - }, - "availability_status": { - "type": "string", - "enum": [ - "available", - "busy", - "offline" - ], - "description": "The availability status of the agent computed by Chatwoot." - }, - "auto_offline": { - "type": "boolean", - "description": "Whether the availability status of agent is configured to go offline automatically when away." - }, - "custom_attributes": { - "type": "object", - "description": "Available for users who are created through platform APIs and has custom attributes associated." - } - } - }, - "inbox": { - "type": "object", - "properties": { - "id": { - "type": "number", - "description": "ID of the inbox" - }, - "name": { - "type": "string", - "description": "The name of the inbox" - }, - "website_url": { - "type": "string", - "description": "Website URL" - }, - "channel_type": { - "type": "string", - "description": "The type of the inbox" - }, - "avatar_url": { - "type": "string", - "description": "The avatar image of the inbox" - }, - "widget_color": { - "type": "string", - "description": "Widget Color used for customization of the widget" - }, - "website_token": { - "type": "string", - "description": "Website Token" - }, - "enable_auto_assignment": { - "type": "boolean", - "description": "The flag which shows whether Auto Assignment is enabled or not" - }, - "web_widget_script": { - "type": "string", - "description": "Script used to load the website widget" - }, - "welcome_title": { - "type": "string", - "description": "Welcome title to be displayed on the widget" - }, - "welcome_tagline": { - "type": "string", - "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", - "description": "A greeting message when the user starts the conversation" - } - } - }, - "agent_bot": { - "type": "object", - "properties": { - "id": { - "type": "number", - "description": "ID of the agent bot" - }, - "name": { - "type": "string", - "description": "The name of the agent bot" - }, - "description": { - "type": "string", - "description": "The description about the agent bot" - }, - "account_id": { - "type": "number", - "description": "Account ID if it's an account specific bot" - }, - "outgoing_url": { - "type": "string", - "description": "The webhook URL for the bot" - } - } - }, - "contact_inboxes": { - "type": "object", - "properties": { - "source_id": { - "type": "string", - "description": "Contact Inbox Source Id" - }, - "inbox": { - "$ref": "#/definitions/inbox" - } - } - }, - "contactable_inboxes": { - "type": "object", - "properties": { - "source_id": { - "type": "string", - "description": "Contact Inbox Source Id" - }, - "inbox": { - "$ref": "#/definitions/inbox" - } - } - }, - "custom_filter": { - "type": "object", - "properties": { - "id": { - "type": "number", - "description": "The ID of the custom filter" - }, - "name": { - "type": "string", - "description": "The name of the custom filter" - }, - "type": { - "type": "string", - "enum": [ - "conversation", - "contact", - "report" - ], - "description": "The description about the custom filter" - }, - "query": { - "type": "object", - "description": "A query that needs to be saved as a custom filter" - }, - "created_at": { - "type": "string", - "format": "date-time", - "description": "The time at which the custom filter was created" - }, - "updated_at": { - "type": "string", - "format": "date-time", - "description": "The time at which the custom filter was updated" - } - } - }, - "webhook": { - "type": "object", - "properties": { - "id": { - "type": "number", - "description": "The ID of the webhook" - }, - "url": { - "type": "string", - "description": "The url to which the events will be send" - }, - "subscriptions": { - "type": "array", - "items": { - "type": "string", - "enum": [ - "conversation_created", - "conversation_status_changed", - "conversation_updated", - "contact_created", - "contact_updated", - "message_created", - "message_updated", - "webwidget_triggered" - ] - }, - "description": "The list of subscribed events" - }, - "account_id": { - "type": "number", - "description": "The id of the account which the webhook object belongs to" - } - } - }, - "account": { - "type": "object", - "properties": { - "id": { - "type": "number", - "description": "Account ID" - }, - "name": { - "type": "string", - "description": "Name of the account" - }, - "role": { - "type": "string", - "enum": [ - "administrator", - "agent" - ], - "description": "The user role in the account" - } - } - }, - "platform_account": { - "type": "object", - "properties": { - "id": { - "type": "number", - "description": "Account ID" - }, - "name": { - "type": "string", - "description": "Name of the account" - } - } - }, - "team": { - "type": "object", - "properties": { - "id": { - "type": "number", - "description": "The ID of the team" - }, - "name": { - "type": "string", - "description": "The name of the team" - }, - "description": { - "type": "string", - "description": "The description about the team" - }, - "allow_auto_assign": { - "type": "boolean", - "description": "If this setting is turned on, the system would automatically assign the conversation to an agent in the team while assigning the conversation to a team" - }, - "account_id": { - "type": "number", - "description": "The ID of the account with the team is a part of" - }, - "is_member": { - "type": "boolean", - "description": "This field shows whether the current user is a part of the team" - } - } - }, - "integrations_app": { - "type": "object", - "properties": { - "id": { - "type": "string", - "description": "The ID of the integration" - }, - "name": { - "type": "string", - "description": "The name of the integration" - }, - "description": { - "type": "string", - "description": "The description about the team" - }, - "hook_type": { - "type": "string", - "description": "Whether the integration is an account or inbox integration" - }, - "enabled": { - "type": "boolean", - "description": "Whether the integration is enabled for the account" - }, - "allow_multiple_hooks": { - "type": "boolean", - "description": "Whether multiple hooks can be created for the integration" - }, - "hooks": { - "type": "array", - "items": { - "type": "object" - }, - "description": "If there are any hooks created for this integration" - } - } - }, - "integrations_hook": { - "type": "object", - "properties": { - "id": { - "type": "string", - "description": "The ID of the integration hook" - }, - "app_id": { - "type": "string", - "description": "The ID of the integration app" - }, - "inbox_id": { - "type": "string", - "description": "Inbox ID if its an Inbox integration" - }, - "account_id": { - "type": "string", - "description": "Account ID of the integration" - }, - "status": { - "type": "boolean", - "description": "Whether the integration hook is enabled for the account" - }, - "hook_type": { - "type": "boolean", - "description": "Whether its an account or inbox integration hook" - }, - "settings": { - "type": "object", - "description": "The associated settings for the integration" - } - } - }, - "public_contact": { - "type": "object", - "properties": { - "id": { - "type": "integer", - "description": "Id of the contact" - }, - "source_id": { - "type": "string", - "description": "The session identifier of the contact" - }, - "name": { - "type": "string", - "description": "Name of the contact" - }, - "email": { - "type": "string", - "description": "Email of the contact" - }, - "pubsub_token": { - "type": "string", - "description": "The token to be used to connect to chatwoot websocket" - } - } - }, - "public_conversation": { - "type": "object", - "properties": { - "id": { - "type": "integer", - "description": "Id of the conversation" - }, - "inbox_id": { - "type": "string", - "description": "The inbox id of the conversation" - }, - "messages": { - "type": "array", - "items": { - "$ref": "#/definitions/message" - }, - "description": "Messages in the conversation" - }, - "contact": { - "type": "object", - "description": "The contact information associated to the conversation" - } - } - }, - "public_message": { - "type": "object", - "properties": { - "id": { - "type": "string", - "description": "Id of the message" - }, - "content": { - "type": "string", - "description": "Text content of the message" - }, - "message_type": { - "type": "string", - "description": "Denotes the message type" - }, - "content_type": { - "type": "string", - "description": "Content type of the message" - }, - "content_attributes": { - "type": "string", - "description": "Additional content attributes of the message" - }, - "created_at": { - "type": "string", - "description": "Created at time stamp of the message" - }, - "conversation_id": { - "type": "string", - "description": "Conversation Id of the message" - }, - "attachments": { - "type": "array", - "items": { - "type": "object" - }, - "description": "Attachments if any" - }, - "sender": { - "type": "object", - "description": "Details of the sender" - } - } - }, - "public_inbox": { - "type": "object", - "properties": { - "identifier": { - "type": "string", - "description": "Inbox identifier" - }, - "name": { - "type": "string", - "description": "Name of the inbox" - }, - "timezone": { - "type": "string", - "description": "The timezone defined on the inbox" - }, - "working_hours": { - "type": "array", - "description": "The working hours defined on the inbox", - "items": { - "type": "object", - "properties": { - "day_of_week": { - "type": "integer", - "description": "Day of the week as a number. Sunday -> 0, Saturday -> 6" - }, - "open_all_day": { - "type": "boolean", - "description": "Whether or not the business is open the whole day" - }, - "closed_all_day": { - "type": "boolean", - "description": "Whether or not the business is closed the whole day" - }, - "open_hour": { - "type": "integer", - "description": "Opening hour. Can be null if closed all day" - }, - "open_minutes": { - "type": "integer", - "description": "Opening minute. Can be null if closed all day" - }, - "close_hour": { - "type": "integer", - "description": "Closing hour. Can be null if closed all day" - }, - "close_minutes": { - "type": "integer", - "description": "Closing minute. Can be null if closed all day" + ], + "get": { + "tags": [ + "Conversation" + ], + "summary": "Get messages from a conversation", + "description": "Returns all messages from a specific conversation", + "operationId": "getConversationMessages", + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/conversation_messages" + } } } } - }, - "working_hours_enabled": { - "type": "boolean", - "description": "Whether of not the working hours are enabled on the inbox" - }, - "csat_survey_enabled": { - "type": "boolean", - "description": "Whether of not the Customer Satisfaction survey is enabled on the inbox" - }, - "greeting_enabled": { - "type": "boolean", - "description": "Whether of not the Greeting Message is enabled on the inbox" - }, - "identity_validation_enabled": { - "type": "boolean", - "description": "Whether of not the User Identity Validation is enforced on the inbox" } } - }, - "account_create_update_payload": { - "type": "object", - "properties": { - "name": { - "type": "string", - "description": "Name of the account" + } + }, + "components": { + "schemas": { + "bad_request_error": { + "title": "data", + "type": "object", + "properties": { + "description": { + "type": "string" + }, + "errors": { + "type": "array", + "items": { + "$ref": "#/components/schemas/request_error" + } + } } - } - }, - "agent_bot_create_update_payload": { - "type": "object", - "properties": { - "name": { - "type": "string", - "description": "The name of the agent bot" - }, - "description": { - "type": "string", - "description": "The description about the agent bot" - }, - "outgoing_url": { - "type": "string", - "description": "The webhook URL for the bot" - } - } - }, - "user_create_update_payload": { - "type": "object", - "properties": { - "name": { - "type": "string", - "description": "Name of the user" - }, - "email": { - "type": "string", - "description": "Email of the user" - }, - "password": { - "type": "string", - "description": "Password must contain uppercase, lowercase letters, number and a special character" - }, - "custom_attributes": { - "type": "object", - "description": "Custom attributes you want to associate with the user" - } - } - }, - "canned_response_create_update_payload": { - "type": "object", - "properties": { - "content": { - "type": "string", - "description": "Message content for canned response" - }, - "short_code": { - "type": "string", - "description": "Short Code for quick access of the canned response" - } - } - }, - "custom_attribute_create_update_payload": { - "type": "object", - "properties": { - "attribute_display_name": { - "type": "string", - "description": "Attribute display name" - }, - "attribute_display_type": { - "type": "integer", - "description": "Attribute display type (text- 0, number- 1, currency- 2, percent- 3, link- 4, date- 5, list- 6, checkbox- 7)" - }, - "attribute_description": { - "type": "string", - "description": "Attribute description" - }, - "attribute_key": { - "type": "string", - "description": "Attribute unique key value" - }, - "attribute_values": { - "type": "array", - "description": "Attribute values", - "items": { + }, + "request_error": { + "type": "object", + "properties": { + "field": { + "type": "string" + }, + "message": { + "type": "string" + }, + "code": { "type": "string" } - }, - "attribute_model": { - "type": "integer", - "description": "Attribute type(conversation_attribute- 0, contact_attribute- 1)" } - } - }, - "contact_create": { - "type": "object", - "required": [ - "inbox_id" - ], - "properties": { - "inbox_id": { - "type": "number" - }, - "name": { - "type": "string", - "description": "name of the contact" - }, - "email": { - "type": "string", - "description": "email of the contact" - }, - "phone_number": { - "type": "string", - "description": "phone number of the contact" - }, - "avatar": { - "type": "string", - "format": "binary", - "description": "Send the form data with the avatar image binary or use the avatar_url" - }, - "avatar_url": { - "type": "string", - "description": "The url to a jpeg, png file for the contact avatar" - }, - "identifier": { - "type": "string", - "description": "A unique identifier for the contact in external system" - }, - "custom_attributes": { - "type": "object", - "description": "An object where you can store custom attributes for contact. example {\"type\":\"customer\", \"age\":30}" + }, + "generic_id": { + "type": "object", + "properties": { + "id": { + "type": "number" + } } - } - }, - "contact_update": { - "type": "object", - "properties": { - "name": { - "type": "string", - "description": "name of the contact" - }, - "email": { - "type": "string", - "description": "email of the contact" - }, - "phone_number": { - "type": "string", - "description": "phone number of the contact" - }, - "avatar": { - "type": "string", - "format": "binary", - "description": "Send the form data with the avatar image binary or use the avatar_url" - }, - "avatar_url": { - "type": "string", - "description": "The url to a jpeg, png file for the contact avatar" - }, - "identifier": { - "type": "string", - "description": "A unique identifier for the contact in external system" - }, - "custom_attributes": { - "type": "object", - "description": "An object where you can store custom attributes for contact. example {\"type\":\"customer\", \"age\":30}" + }, + "canned_response": { + "type": "object", + "properties": { + "id": { + "type": "integer", + "description": "ID of the canned response" + }, + "account_id": { + "type": "integer", + "description": "Account Id" + }, + "short_code": { + "type": "string", + "description": "Short Code for quick access of the canned response" + }, + "content": { + "type": "string", + "description": "Message content for canned response" + }, + "created_at": { + "type": "string", + "description": "The date and time when the canned response was created" + }, + "updated_at": { + "type": "string", + "description": "The date and time when the canned response was updated" + } } - } - }, - "conversation_message_create": { - "type": "object", - "required": [ - "content" - ], - "properties": { - "content": { - "type": "string", - "description": "The content of the message" - }, - "message_type": { - "type": "string", - "enum": [ - "outgoing", - "incoming" - ] - }, - "private": { - "type": "boolean", - "description": "Flag to identify if it is a private note" - }, - "content_type": { - "type": "string", - "enum": [ - "text", - "input_email", - "cards", - "input_select", - "form", - "article" - ], - "example": "cards", - "description": "if you want to create custom message types" - }, - "content_attributes": { - "type": "object", - "description": "attributes based on your content type" - }, - "template_params": { - "type": "object", - "description": "The template params for the message in case of whatsapp Channel", - "properties": { - "name": { - "type": "string", - "description": "Name of the template", - "example": "sample_issue_resolution" - }, - "category": { - "type": "string", - "description": "Category of the template", - "example": "UTILITY" - }, - "language": { - "type": "string", - "description": "Language of the template", - "example": "en_US" - }, - "processed_params": { + }, + "custom_attribute": { + "type": "object", + "properties": { + "id": { + "type": "integer", + "description": "Identifier" + }, + "attribute_display_name": { + "type": "string", + "description": "Attribute display name" + }, + "attribute_display_type": { + "type": "string", + "description": "Attribute display type (text, number, currency, percent, link, date, list, checkbox)" + }, + "attribute_description": { + "type": "string", + "description": "Attribute description" + }, + "attribute_key": { + "type": "string", + "description": "Attribute unique key value" + }, + "regex_pattern": { + "type": "string", + "description": "Regex pattern" + }, + "regex_cue": { + "type": "string", + "description": "Regex cue" + }, + "attribute_values": { + "type": "string", + "description": "Attribute values" + }, + "attribute_model": { + "type": "string", + "description": "Attribute type(conversation_attribute/contact_attribute)" + }, + "default_value": { + "type": "string", + "description": "Attribute default value" + }, + "created_at": { + "type": "string", + "description": "The date and time when the custom attribute was created" + }, + "updated_at": { + "type": "string", + "description": "The date and time when the custom attribute was updated" + } + } + }, + "automation_rule": { + "type": "object", + "properties": { + "payload": { + "description": "Response payload that contains automation rule(s)", + "oneOf": [ + { + "type": "array", + "description": "Array of automation rules (for listing endpoint)", + "items": { + "$ref": "#/components/schemas/automation_rule_item" + } + }, + { + "type": "object", + "description": "Single automation rule (for show/create/update endpoints)", + "allOf": [ + { + "$ref": "#/components/schemas/automation_rule_item" + } + ] + } + ] + } + } + }, + "automation_rule_item": { + "type": "object", + "properties": { + "id": { + "type": "integer", + "description": "The ID of the automation rule" + }, + "account_id": { + "type": "integer", + "description": "Account Id" + }, + "name": { + "type": "string", + "description": "The name of the rule", + "example": "Add label on message create event" + }, + "description": { + "type": "string", + "description": "Description to give more context about the rule", + "example": "Add label support and sales on message create event if incoming message content contains text help" + }, + "event_name": { + "type": "string", + "description": "Automation Rule event, on which we call the actions(conversation_created, conversation_updated, message_created)", + "enum": [ + "conversation_created", + "conversation_updated", + "message_created" + ], + "example": "message_created" + }, + "conditions": { + "type": "array", + "description": "Array of conditions on which conversation/message filter would work", + "items": { "type": "object", - "description": "The processed param values for template variables in template", + "properties": { + "values": { + "type": "array", + "items": { + "type": "string" + } + }, + "attribute_key": { + "type": "string" + }, + "query_operator": { + "type": "string" + }, + "filter_operator": { + "type": "string" + } + }, "example": { - "1": "Chatwoot" + "attribute_key": "content", + "filter_operator": "contains", + "values": [ + "help" + ], + "query_operator": "and" + } + } + }, + "actions": { + "type": "array", + "description": "Array of actions which we perform when condition matches", + "items": { + "type": "object", + "properties": { + "action_name": { + "type": "string" + }, + "action_params": { + "type": "array", + "items": { + "type": "string" + } + } + }, + "example": { + "action_name": "add_label", + "action_params": [ + "support", + "sales" + ] + } + } + }, + "created_on": { + "type": "integer", + "description": "The timestamp when the rule was created" + }, + "active": { + "type": "boolean", + "description": "Enable/disable automation rule" + } + } + }, + "portal": { + "type": "object", + "properties": { + "payload": { + "type": "array", + "items": { + "$ref": "#/components/schemas/portal_item" + } + } + } + }, + "portal_single": { + "type": "object", + "properties": { + "payload": { + "$ref": "#/components/schemas/portal_item" + } + } + }, + "portal_config": { + "type": "object", + "description": "Configuration settings for the portal", + "properties": { + "allowed_locales": { + "type": "array", + "description": "List of allowed locales for the portal", + "items": { + "type": "object", + "properties": { + "code": { + "type": "string", + "description": "The language code" + }, + "articles_count": { + "type": "integer", + "description": "Number of articles in this locale" + }, + "categories_count": { + "type": "integer", + "description": "Number of categories in this locale" + } } } } } - } - }, - "team_create_update_payload": { - "type": "object", - "properties": { - "name": { - "type": "string", - "description": "The name of the team" - }, - "description": { - "type": "string", - "description": "The description of the team" - }, - "allow_auto_assign": { - "type": "boolean", - "description": "If this setting is turned on, the system would automatically assign the conversation to an agent in the team while assigning the conversation to a team" + }, + "portal_logo": { + "type": "object", + "properties": { + "id": { + "type": "integer", + "description": "ID of the logo file" + }, + "portal_id": { + "type": "integer", + "description": "ID of the portal this logo belongs to" + }, + "file_type": { + "type": "string", + "description": "MIME type of the file" + }, + "account_id": { + "type": "integer", + "description": "ID of the account" + }, + "file_url": { + "type": "string", + "description": "URL to access the logo file" + }, + "blob_id": { + "type": "integer", + "description": "ID of the blob" + }, + "filename": { + "type": "string", + "description": "Name of the file" + } } - } - }, - "custom_filter_create_update_payload": { - "type": "object", - "properties": { - "name": { - "type": "string", - "description": "The name of the custom filter" - }, - "type": { - "type": "string", - "enum": [ - "conversation", - "contact", - "report" - ], - "description": "The description about the custom filter" - }, - "query": { + }, + "portal_meta": { + "type": "object", + "properties": { + "all_articles_count": { + "type": "integer", + "description": "Total number of articles" + }, + "archived_articles_count": { + "type": "integer", + "nullable": true, + "description": "Number of archived articles" + }, + "published_count": { + "type": "integer", + "nullable": true, + "description": "Number of published articles" + }, + "draft_articles_count": { + "type": "integer", + "nullable": true, + "description": "Number of draft articles" + }, + "categories_count": { + "type": "integer", + "description": "Number of categories" + }, + "default_locale": { + "type": "string", + "description": "Default locale for the portal" + } + } + }, + "portal_item": { + "type": "object", + "properties": { + "id": { + "type": "integer", + "description": "The ID of the portal" + }, + "archived": { + "type": "boolean", + "description": "Whether the portal is archived" + }, + "color": { + "type": "string", + "description": "The color code for the portal" + }, + "config": { + "$ref": "#/components/schemas/portal_config" + }, + "custom_domain": { + "type": "string", + "description": "Custom domain for the portal" + }, + "header_text": { + "type": "string", + "description": "The header text for the portal" + }, + "homepage_link": { + "type": "string", + "description": "Homepage link for the portal" + }, + "name": { + "type": "string", + "description": "Name of the portal" + }, + "slug": { + "type": "string", + "description": "URL slug for the portal" + }, + "page_title": { + "type": "string", + "description": "Page title for the portal" + }, + "account_id": { + "type": "integer", + "description": "ID of the account the portal belongs to" + }, + "inbox": { + "$ref": "#/components/schemas/inbox" + }, + "logo": { + "$ref": "#/components/schemas/portal_logo" + }, + "meta": { + "$ref": "#/components/schemas/portal_meta" + } + } + }, + "category": { + "type": "object", + "properties": { + "id": { + "type": "integer" + }, + "description": { + "type": "string", + "description": "The text content." + }, + "locale": { + "type": "string" + }, + "name": { + "type": "string" + }, + "slug": { + "type": "string" + }, + "position": { + "type": "integer" + }, + "portal_id": { + "type": "integer" + }, + "account_id": { + "type": "integer" + }, + "associated_category_id": { + "type": "integer", + "description": "To associate similar categories to each other, e.g same category of product documentation in different languages" + }, + "parent_category_id": { + "type": "integer", + "description": "To define parent category, e.g product documentation has multiple level features in sales category or in engineering category." + } + } + }, + "article": { + "type": "object", + "properties": { + "id": { + "type": "integer" + }, + "content": { + "type": "string", + "description": "The text content." + }, + "meta": { + "type": "object" + }, + "position": { + "type": "integer" + }, + "status": { + "type": "integer", + "enum": [ + "draft", + "published", + "archived" + ] + }, + "title": { + "type": "string" + }, + "slug": { + "type": "string" + }, + "views": { + "type": "integer" + }, + "portal_id": { + "type": "integer" + }, + "account_id": { + "type": "integer" + }, + "author_id": { + "type": "integer" + }, + "category_id": { + "type": "integer" + }, + "folder_id": { + "type": "integer" + }, + "associated_article_id": { + "type": "integer", + "description": "To associate similar articles to each other, e.g to provide the link for the reference." + } + } + }, + "contact": { + "type": "object", + "properties": { + "payload": { + "type": "array", + "items": { + "type": "object", + "properties": { + "additional_attributes": { + "type": "object", + "description": "The object containing additional attributes related to the contact" + }, + "availability_status": { + "type": "string", + "description": "The availability status of the contact" + }, + "email": { + "type": "string", + "description": "The email address of the contact" + }, + "id": { + "type": "integer", + "description": "The ID of the contact" + }, + "name": { + "type": "string", + "description": "The name of the contact" + }, + "phone_number": { + "type": "string", + "description": "The phone number of the contact" + }, + "blocked": { + "type": "boolean", + "description": "Whether the contact is blocked" + }, + "identifier": { + "type": "string", + "description": "The identifier of the contact" + }, + "thumbnail": { + "type": "string", + "description": "The thumbnail of the contact" + }, + "custom_attributes": { + "type": "object", + "description": "The custom attributes of the contact", + "example": { + "attribute_key": "attribute_value", + "signed_up_at": "dd/mm/yyyy" + } + }, + "last_activity_at": { + "type": "integer", + "description": "The last activity at of the contact" + }, + "created_at": { + "type": "integer", + "description": "The created at of the contact" + }, + "contact_inboxes": { + "type": "array", + "items": { + "$ref": "#/components/schemas/contact_inboxes" + } + } + } + } + } + } + }, + "conversation": { + "type": "object", + "properties": { + "id": { + "type": "number", + "description": "ID of the conversation" + }, + "messages": { + "type": "array", + "items": { + "$ref": "#/components/schemas/message" + } + }, + "account_id": { + "type": "number", + "description": "Account Id" + }, + "uuid": { + "type": "string", + "description": "UUID of the conversation" + }, + "additional_attributes": { + "type": "object", + "description": "The object containing additional attributes related to the conversation" + }, + "agent_last_seen_at": { + "type": "number", + "description": "The last activity at of the agent" + }, + "assignee_last_seen_at": { + "type": "number", + "description": "The last activity at of the assignee" + }, + "can_reply": { + "type": "boolean", + "description": "Whether the conversation can be replied to" + }, + "contact_last_seen_at": { + "type": "number", + "description": "The last activity at of the contact" + }, + "custom_attributes": { + "type": "object", + "description": "The object to save custom attributes for conversation, accepts custom attributes key and value" + }, + "inbox_id": { + "type": "number", + "description": "ID of the inbox" + }, + "labels": { + "type": "array", + "items": { + "type": "string" + }, + "description": "The labels of the conversation" + }, + "muted": { + "type": "boolean", + "description": "Whether the conversation is muted" + }, + "snoozed_until": { + "type": "number", + "description": "The time at which the conversation will be unmuted" + }, + "status": { + "type": "string", + "enum": [ + "open", + "resolved", + "pending" + ], + "description": "The status of the conversation" + }, + "created_at": { + "type": "number", + "description": "The time at which conversation was created" + }, + "updated_at": { + "type": "number", + "description": "The time at which conversation was updated" + }, + "timestamp": { + "type": "string", + "description": "The time at which conversation was created" + }, + "first_reply_created_at": { + "type": "number", + "description": "The time at which the first reply was created" + }, + "unread_count": { + "type": "number", + "description": "The number of unread messages" + }, + "last_non_activity_message": { + "$ref": "#/components/schemas/message" + }, + "last_activity_at": { + "type": "number", + "description": "The last activity at of the conversation" + }, + "priority": { + "type": "string", + "description": "The priority of the conversation" + }, + "waiting_since": { + "type": "number", + "description": "The time at which the conversation was waiting" + }, + "sla_policy_id": { + "type": "number", + "description": "The ID of the SLA policy" + }, + "applied_sla": { + "type": "object", + "description": "The applied SLA" + }, + "sla_events": { + "type": "array", + "items": { + "type": "object", + "description": "SLA event objects" + } + } + } + }, + "message": { + "type": "object", + "properties": { + "id": { + "type": "number", + "description": "The ID of the message" + }, + "content": { + "type": "string", + "description": "The text content of the message" + }, + "account_id": { + "type": "number", + "description": "The ID of the account" + }, + "inbox_id": { + "type": "number", + "description": "The ID of the inbox" + }, + "conversation_id": { + "type": "number", + "description": "The ID of the conversation" + }, + "message_type": { + "type": "integer", + "enum": [ + 0, + 1, + 2 + ], + "description": "The type of the message" + }, + "created_at": { + "type": "integer", + "description": "The time at which message was created" + }, + "updated_at": { + "type": "integer", + "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" + ], + "description": "The status of the message" + }, + "source_id": { + "type": "string", + "description": "The source ID of the message" + }, + "content_type": { + "type": "string", + "enum": [ + "text", + "input_select", + "cards", + "form" + ], + "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" + ], + "description": "The type of the sender" + }, + "sender_id": { + "type": "number", + "description": "The ID of the sender" + }, + "external_source_ids": { + "type": "object", + "description": "The external source IDs of the message" + }, + "additional_attributes": { + "type": "object", + "description": "The additional attributes of the message" + }, + "processed_message_content": { + "type": "string", + "description": "The processed message content" + }, + "sentiment": { + "type": "object", + "description": "The sentiment of the message" + }, + "conversation": { + "type": "object", + "description": "The conversation object" + }, + "attachment": { + "type": "object", + "description": "The file object attached to the image" + }, + "sender": { + "type": "object", + "description": "User/Agent/AgentBot object" + } + } + }, + "user": { + "type": "object", + "properties": { + "id": { + "type": "number" + }, + "access_token": { + "type": "string" + }, + "account_id": { + "type": "number" + }, + "available_name": { + "type": "string" + }, + "avatar_url": { + "type": "string" + }, + "confirmed": { + "type": "boolean" + }, + "display_name": { + "type": "string", + "nullable": true + }, + "message_signature": { + "type": "string", + "nullable": true + }, + "email": { + "type": "string" + }, + "hmac_identifier": { + "type": "string" + }, + "inviter_id": { + "type": "number" + }, + "name": { + "type": "string" + }, + "provider": { + "type": "string" + }, + "pubsub_token": { + "type": "string" + }, + "role": { + "type": "string", + "enum": [ + "agent", + "administrator" + ] + }, + "ui_settings": { + "type": "object" + }, + "uid": { + "type": "string" + }, + "type": { + "type": "string", + "nullable": true + }, + "custom_attributes": { + "type": "object", + "description": "Available for users who are created through platform APIs and has custom attributes associated." + }, + "accounts": { + "type": "array", + "items": { + "type": "object", + "properties": { + "id": { + "type": "number" + }, + "name": { + "type": "string" + }, + "status": { + "type": "string" + }, + "active_at": { + "type": "string", + "format": "date-time" + }, + "role": { + "type": "string", + "enum": [ + "administrator", + "agent" + ] + }, + "permissions": { + "type": "array", + "items": { + "type": "string" + } + }, + "availability": { + "type": "string" + }, + "availability_status": { + "type": "string" + }, + "auto_offline": { + "type": "boolean" + }, + "custom_role_id": { + "type": "number", + "nullable": true + }, + "custom_role": { + "type": "object", + "nullable": true + } + } + } + } + } + }, + "agent": { + "type": "object", + "properties": { + "id": { + "type": "integer" + }, + "account_id": { + "type": "integer" + }, + "availability_status": { + "type": "string", + "enum": [ + "available", + "busy", + "offline" + ], + "description": "The availability status of the agent computed by Chatwoot." + }, + "auto_offline": { + "type": "boolean", + "description": "Whether the availability status of agent is configured to go offline automatically when away." + }, + "confirmed": { + "type": "boolean", + "description": "Whether the agent has confirmed their email address." + }, + "email": { + "type": "string", + "description": "The email of the agent" + }, + "available_name": { + "type": "string", + "description": "The available name of the agent" + }, + "name": { + "type": "string", + "description": "The name of the agent" + }, + "role": { + "type": "string", + "enum": [ + "agent", + "administrator" + ], + "description": "The role of the agent" + }, + "thumbnail": { + "type": "string", + "description": "The thumbnail of the agent" + }, + "custom_role_id": { + "type": "integer", + "description": "The custom role id of the agent" + } + } + }, + "inbox": { + "type": "object", + "properties": { + "id": { + "type": "number", + "description": "ID of the inbox" + }, + "name": { + "type": "string", + "description": "The name of the inbox" + }, + "website_url": { + "type": "string", + "description": "Website URL" + }, + "channel_type": { + "type": "string", + "description": "The type of the inbox" + }, + "avatar_url": { + "type": "string", + "description": "The avatar image of the inbox" + }, + "widget_color": { + "type": "string", + "description": "Widget Color used for customization of the widget" + }, + "website_token": { + "type": "string", + "description": "Website Token" + }, + "enable_auto_assignment": { + "type": "boolean", + "description": "The flag which shows whether Auto Assignment is enabled or not" + }, + "web_widget_script": { + "type": "string", + "description": "Script used to load the website widget" + }, + "welcome_title": { + "type": "string", + "description": "Welcome title to be displayed on the widget" + }, + "welcome_tagline": { + "type": "string", + "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", + "description": "A greeting message when the user starts the conversation" + }, + "channel_id": { + "type": "number", + "description": "ID of the channel this inbox belongs to" + }, + "working_hours_enabled": { + "type": "boolean", + "description": "The flag which shows whether working hours feature is enabled" + }, + "enable_email_collect": { + "type": "boolean", + "description": "The flag to enable collecting email from contacts" + }, + "csat_survey_enabled": { + "type": "boolean", + "description": "The flag to enable CSAT survey" + }, + "auto_assignment_config": { + "type": "object", + "description": "Configuration settings for auto assignment" + }, + "out_of_office_message": { + "type": "string", + "description": "Message to show when agents are out of office" + }, + "working_hours": { + "type": "array", + "description": "Configuration for working hours of the inbox", + "items": { + "type": "object", + "properties": { + "day_of_week": { + "type": "number", + "description": "Day of the week (0-6, where 0 is Sunday)" + }, + "closed_all_day": { + "type": "boolean", + "description": "Whether the inbox is closed for the entire day" + }, + "open_hour": { + "type": "number", + "description": "Hour when inbox opens (0-23)" + }, + "open_minutes": { + "type": "number", + "description": "Minutes of the hour when inbox opens (0-59)" + }, + "close_hour": { + "type": "number", + "description": "Hour when inbox closes (0-23)" + }, + "close_minutes": { + "type": "number", + "description": "Minutes of the hour when inbox closes (0-59)" + }, + "open_all_day": { + "type": "boolean", + "description": "Whether the inbox is open for the entire day" + } + } + } + }, + "timezone": { + "type": "string", + "description": "Timezone configuration for the inbox" + }, + "callback_webhook_url": { + "type": "string", + "description": "Webhook URL for callbacks" + }, + "allow_messages_after_resolved": { + "type": "boolean", + "description": "Whether to allow messages after a conversation is resolved" + }, + "lock_to_single_conversation": { + "type": "boolean", + "description": "Whether to lock a contact to a single conversation" + }, + "sender_name_type": { + "type": "string", + "description": "Type of sender name to display (e.g., friendly)" + }, + "business_name": { + "type": "string", + "description": "Business name associated with the inbox" + }, + "hmac_mandatory": { + "type": "boolean", + "description": "Whether HMAC verification is mandatory" + }, + "selected_feature_flags": { + "type": "object", + "description": "Selected feature flags for the inbox" + }, + "reply_time": { + "type": "string", + "description": "Expected reply time" + }, + "messaging_service_sid": { + "type": "string", + "description": "Messaging service SID for SMS providers" + }, + "phone_number": { + "type": "string", + "description": "Phone number associated with the inbox" + }, + "medium": { + "type": "string", + "description": "Medium of communication (e.g., sms, email)" + }, + "provider": { + "type": "string", + "description": "Provider of the channel" + } + } + }, + "inbox_contact": { + "type": "object", + "properties": { + "id": { + "type": "number", + "description": "ID of the inbox" + }, + "avatar_url": { + "type": "string", + "description": "The avatar image of the inbox" + }, + "channel_id": { + "type": "number", + "description": "The ID of the channel" + }, + "name": { + "type": "string", + "description": "The name of the inbox" + }, + "channel_type": { + "type": "string", + "description": "The type of the inbox" + }, + "provider": { + "type": "string", + "description": "The provider of the inbox" + } + } + }, + "agent_bot": { + "type": "object", + "properties": { + "id": { + "type": "number", + "description": "ID of the agent bot" + }, + "name": { + "type": "string", + "description": "The name of the agent bot" + }, + "description": { + "type": "string", + "description": "The description about the agent bot" + }, + "thumbnail": { + "type": "string", + "description": "The thumbnail of the agent bot" + }, + "outgoing_url": { + "type": "string", + "description": "The webhook URL for the bot" + }, + "bot_type": { + "type": "string", + "description": "The type of the bot" + }, + "bot_config": { + "type": "object", + "description": "The configuration of the bot" + }, + "account_id": { + "type": "number", + "description": "Account ID if it's an account specific bot" + }, + "access_token": { + "type": "string", + "description": "The access token for the bot" + }, + "system_bot": { + "type": "boolean", + "description": "Whether the bot is a system bot" + } + } + }, + "contact_inboxes": { + "type": "object", + "properties": { + "source_id": { + "type": "string", + "description": "Contact Inbox Source Id" + }, + "inbox": { + "$ref": "#/components/schemas/inbox_contact" + } + } + }, + "contactable_inboxes": { + "type": "object", + "properties": { + "source_id": { + "type": "string", + "description": "Contact Inbox Source Id" + }, + "inbox": { + "$ref": "#/components/schemas/inbox" + } + } + }, + "custom_filter": { + "type": "object", + "properties": { + "id": { + "type": "number", + "description": "The ID of the custom filter" + }, + "name": { + "type": "string", + "description": "The name of the custom filter" + }, + "type": { + "type": "string", + "enum": [ + "conversation", + "contact", + "report" + ], + "description": "The description about the custom filter" + }, + "query": { + "type": "object", + "description": "A query that needs to be saved as a custom filter" + }, + "created_at": { + "type": "string", + "format": "date-time", + "description": "The time at which the custom filter was created" + }, + "updated_at": { + "type": "string", + "format": "date-time", + "description": "The time at which the custom filter was updated" + } + } + }, + "webhook": { + "type": "object", + "properties": { + "id": { + "type": "number", + "description": "The ID of the webhook" + }, + "url": { + "type": "string", + "description": "The url to which the events will be send" + }, + "subscriptions": { + "type": "array", + "items": { + "type": "string", + "enum": [ + "conversation_created", + "conversation_status_changed", + "conversation_updated", + "contact_created", + "contact_updated", + "message_created", + "message_updated", + "webwidget_triggered" + ] + }, + "description": "The list of subscribed events" + }, + "account_id": { + "type": "number", + "description": "The id of the account which the webhook object belongs to" + } + } + }, + "account": { + "type": "object", + "properties": { + "id": { + "type": "number", + "description": "Account ID" + }, + "name": { + "type": "string", + "description": "Name of the account" + }, + "role": { + "type": "string", + "enum": [ + "administrator", + "agent" + ], + "description": "The user role in the account" + } + } + }, + "account_user": { + "type": "array", + "description": "Array of account users", + "items": { "type": "object", - "description": "A query that needs to be saved as a custom filter" + "properties": { + "account_id": { + "type": "integer", + "description": "The ID of the account" + }, + "user_id": { + "type": "integer", + "description": "The ID of the user" + }, + "role": { + "type": "string", + "description": "whether user is an administrator or agent" + } + } } - } - }, - "webhook_create_update_payload": { - "type": "object", - "properties": { - "url": { - "type": "string", - "description": "The url where the events should be sent" - }, - "subscriptions": { - "type": "array", - "items": { + }, + "platform_account": { + "type": "object", + "properties": { + "id": { + "type": "number", + "description": "Account ID" + }, + "name": { + "type": "string", + "description": "Name of the account" + } + } + }, + "team": { + "type": "object", + "properties": { + "id": { + "type": "number", + "description": "The ID of the team" + }, + "name": { + "type": "string", + "description": "The name of the team" + }, + "description": { + "type": "string", + "description": "The description about the team" + }, + "allow_auto_assign": { + "type": "boolean", + "description": "If this setting is turned on, the system would automatically assign the conversation to an agent in the team while assigning the conversation to a team" + }, + "account_id": { + "type": "number", + "description": "The ID of the account with the team is a part of" + }, + "is_member": { + "type": "boolean", + "description": "This field shows whether the current user is a part of the team" + } + } + }, + "integrations_app": { + "type": "object", + "properties": { + "id": { + "type": "string", + "description": "The ID of the integration" + }, + "name": { + "type": "string", + "description": "The name of the integration" + }, + "description": { + "type": "string", + "description": "The description about the team" + }, + "hook_type": { + "type": "string", + "description": "Whether the integration is an account or inbox integration" + }, + "enabled": { + "type": "boolean", + "description": "Whether the integration is enabled for the account" + }, + "allow_multiple_hooks": { + "type": "boolean", + "description": "Whether multiple hooks can be created for the integration" + }, + "hooks": { + "type": "array", + "items": { + "type": "object" + }, + "description": "If there are any hooks created for this integration" + } + } + }, + "integrations_hook": { + "type": "object", + "properties": { + "id": { + "type": "string", + "description": "The ID of the integration hook" + }, + "app_id": { + "type": "string", + "description": "The ID of the integration app" + }, + "inbox_id": { + "type": "string", + "description": "Inbox ID if its an Inbox integration" + }, + "account_id": { + "type": "string", + "description": "Account ID of the integration" + }, + "status": { + "type": "boolean", + "description": "Whether the integration hook is enabled for the account" + }, + "hook_type": { + "type": "boolean", + "description": "Whether its an account or inbox integration hook" + }, + "settings": { + "type": "object", + "description": "The associated settings for the integration" + } + } + }, + "public_contact": { + "type": "object", + "properties": { + "id": { + "type": "integer", + "description": "Id of the contact" + }, + "source_id": { + "type": "string", + "description": "The session identifier of the contact" + }, + "name": { + "type": "string", + "description": "Name of the contact" + }, + "email": { + "type": "string", + "description": "Email of the contact" + }, + "pubsub_token": { + "type": "string", + "description": "The token to be used to connect to chatwoot websocket" + } + } + }, + "public_conversation": { + "type": "object", + "properties": { + "id": { + "type": "integer", + "description": "Id of the conversation" + }, + "inbox_id": { + "type": "string", + "description": "The inbox id of the conversation" + }, + "messages": { + "type": "array", + "items": { + "$ref": "#/components/schemas/message" + }, + "description": "Messages in the conversation" + }, + "contact": { + "type": "object", + "description": "The contact information associated to the conversation" + } + } + }, + "public_message": { + "type": "object", + "properties": { + "id": { + "type": "string", + "description": "Id of the message" + }, + "content": { + "type": "string", + "description": "Text content of the message" + }, + "message_type": { + "type": "string", + "description": "Denotes the message type" + }, + "content_type": { + "type": "string", + "description": "Content type of the message" + }, + "content_attributes": { + "type": "string", + "description": "Additional content attributes of the message" + }, + "created_at": { + "type": "string", + "description": "Created at time stamp of the message" + }, + "conversation_id": { + "type": "string", + "description": "Conversation Id of the message" + }, + "attachments": { + "type": "array", + "items": { + "type": "object" + }, + "description": "Attachments if any" + }, + "sender": { + "type": "object", + "description": "Details of the sender" + } + } + }, + "public_inbox": { + "type": "object", + "properties": { + "identifier": { + "type": "string", + "description": "Inbox identifier" + }, + "name": { + "type": "string", + "description": "Name of the inbox" + }, + "timezone": { + "type": "string", + "description": "The timezone defined on the inbox" + }, + "working_hours": { + "type": "array", + "description": "The working hours defined on the inbox", + "items": { + "type": "object", + "properties": { + "day_of_week": { + "type": "integer", + "description": "Day of the week as a number. Sunday -> 0, Saturday -> 6" + }, + "open_all_day": { + "type": "boolean", + "description": "Whether or not the business is open the whole day" + }, + "closed_all_day": { + "type": "boolean", + "description": "Whether or not the business is closed the whole day" + }, + "open_hour": { + "type": "integer", + "description": "Opening hour. Can be null if closed all day" + }, + "open_minutes": { + "type": "integer", + "description": "Opening minute. Can be null if closed all day" + }, + "close_hour": { + "type": "integer", + "description": "Closing hour. Can be null if closed all day" + }, + "close_minutes": { + "type": "integer", + "description": "Closing minute. Can be null if closed all day" + } + } + } + }, + "working_hours_enabled": { + "type": "boolean", + "description": "Whether of not the working hours are enabled on the inbox" + }, + "csat_survey_enabled": { + "type": "boolean", + "description": "Whether of not the Customer Satisfaction survey is enabled on the inbox" + }, + "greeting_enabled": { + "type": "boolean", + "description": "Whether of not the Greeting Message is enabled on the inbox" + }, + "identity_validation_enabled": { + "type": "boolean", + "description": "Whether of not the User Identity Validation is enforced on the inbox" + } + } + }, + "account_create_update_payload": { + "type": "object", + "properties": { + "name": { + "type": "string", + "description": "Name of the account", + "example": "My Account" + }, + "locale": { + "type": "string", + "description": "The locale of the account", + "example": "en" + }, + "domain": { + "type": "string", + "description": "The domain of the account", + "example": "example.com" + }, + "support_email": { + "type": "string", + "description": "The support email of the account", + "example": "support@example.com" + }, + "status": { + "type": "string", + "enum": [ + "active", + "suspended" + ], + "description": "The status of the account", + "example": "active" + }, + "limits": { + "type": "object", + "description": "The limits of the account", + "example": {} + }, + "custom_attributes": { + "type": "object", + "description": "The custom attributes of the account", + "example": {} + } + } + }, + "account_user_create_update_payload": { + "type": "object", + "required": [ + "user_id", + "role" + ], + "properties": { + "user_id": { + "type": "integer", + "description": "The ID of the user", + "example": 1 + }, + "role": { + "type": "string", + "description": "whether user is an administrator or agent", + "example": "administrator" + } + } + }, + "platform_agent_bot_create_update_payload": { + "type": "object", + "properties": { + "name": { + "type": "string", + "description": "The name of the agent bot", + "example": "My Agent Bot" + }, + "description": { + "type": "string", + "description": "The description of the agent bot", + "example": "This is a sample agent bot" + }, + "outgoing_url": { + "type": "string", + "description": "The webhook URL for the bot", + "example": "https://example.com/webhook" + }, + "account_id": { + "type": "integer", + "description": "The account ID to associate the agent bot with", + "example": 1 + }, + "avatar": { + "type": "string", + "format": "binary", + "description": "Send the form data with the avatar image binary or use the avatar_url" + }, + "avatar_url": { + "type": "string", + "description": "The url to a jpeg, png file for the agent bot avatar", + "example": "https://example.com/avatar.png" + } + } + }, + "agent_bot_create_update_payload": { + "type": "object", + "properties": { + "name": { + "type": "string", + "description": "The name of the agent bot", + "example": "My Agent Bot" + }, + "description": { + "type": "string", + "description": "The description of the agent bot", + "example": "This is a sample agent bot" + }, + "outgoing_url": { + "type": "string", + "description": "The webhook URL for the bot", + "example": "https://example.com/webhook" + }, + "avatar": { + "type": "string", + "format": "binary", + "description": "Send the form data with the avatar image binary or use the avatar_url" + }, + "avatar_url": { + "type": "string", + "description": "The url to a jpeg, png file for the agent bot avatar", + "example": "https://example.com/avatar.png" + }, + "bot_type": { + "type": "integer", + "description": "The type of the bot (0 for webhook)", + "example": 0 + }, + "bot_config": { + "type": "object", + "description": "The configuration for the bot", + "example": {} + } + } + }, + "user_create_update_payload": { + "type": "object", + "properties": { + "name": { + "type": "string", + "description": "Name of the user", + "example": "Daniel" + }, + "display_name": { + "type": "string", + "description": "Display name of the user", + "example": "Dan" + }, + "email": { + "type": "string", + "description": "Email of the user", + "example": "daniel@acme.inc" + }, + "password": { + "type": "string", + "description": "Password must contain uppercase, lowercase letters, number and a special character", + "example": "Password2!" + }, + "custom_attributes": { + "type": "object", + "description": "Custom attributes you want to associate with the user", + "example": {} + } + } + }, + "canned_response_create_update_payload": { + "type": "object", + "properties": { + "content": { + "type": "string", + "description": "Message content for canned response", + "example": "Hello, {{contact.name}}! Welcome to our service." + }, + "short_code": { + "type": "string", + "description": "Short Code for quick access of the canned response", + "example": "welcome" + } + } + }, + "custom_attribute_create_update_payload": { + "type": "object", + "properties": { + "attribute_display_name": { + "type": "string", + "description": "Attribute display name", + "example": "Custom Attribute" + }, + "attribute_display_type": { + "type": "integer", + "description": "Attribute display type (text- 0, number- 1, currency- 2, percent- 3, link- 4, date- 5, list- 6, checkbox- 7)", + "example": 0 + }, + "attribute_description": { + "type": "string", + "description": "Attribute description", + "example": "This is a custom attribute" + }, + "attribute_key": { + "type": "string", + "description": "Attribute unique key value", + "example": "custom_attribute" + }, + "attribute_values": { + "type": "array", + "description": "Attribute values", + "items": { + "type": "string" + }, + "example": [ + "value1", + "value2" + ] + }, + "attribute_model": { + "type": "integer", + "description": "Attribute type(conversation_attribute- 0, contact_attribute- 1)", + "example": 0 + }, + "regex_pattern": { + "type": "string", + "description": "Regex pattern (Only applicable for type- text). The regex pattern is used to validate the attribute value(s).", + "example": "^[a-zA-Z0-9]+$" + }, + "regex_cue": { + "type": "string", + "description": "Regex cue message (Only applicable for type- text). The cue message is shown when the regex pattern is not matched.", + "example": "Please enter a valid value" + } + } + }, + "agent_create_payload": { + "type": "object", + "required": [ + "name", + "email", + "role" + ], + "properties": { + "name": { + "type": "string", + "description": "Full Name of the agent", + "example": "John Doe" + }, + "email": { + "type": "string", + "description": "Email of the Agent", + "example": "john.doe@acme.inc" + }, + "role": { + "type": "string", + "enum": [ + "agent", + "administrator" + ], + "description": "Whether its administrator or agent", + "example": "agent" + }, + "availability_status": { + "type": "string", + "enum": [ + "available", + "busy", + "offline" + ], + "description": "The availability setting of the agent.", + "example": "available" + }, + "auto_offline": { + "type": "boolean", + "description": "Whether the availability status of agent is configured to go offline automatically when away.", + "example": true + } + } + }, + "agent_update_payload": { + "type": "object", + "required": [ + "role" + ], + "properties": { + "role": { + "type": "string", + "enum": [ + "agent", + "administrator" + ], + "description": "Whether its administrator or agent", + "example": "agent" + }, + "availability_status": { + "type": "string", + "enum": [ + "available", + "busy", + "offline" + ], + "description": "The availability status of the agent.", + "example": "available" + }, + "auto_offline": { + "type": "boolean", + "description": "Whether the availability status of agent is configured to go offline automatically when away.", + "example": true + } + } + }, + "contact_create_payload": { + "type": "object", + "required": [ + "inbox_id" + ], + "properties": { + "inbox_id": { + "type": "number", + "description": "ID of the inbox to which the contact belongs", + "example": 1 + }, + "name": { + "type": "string", + "description": "name of the contact", + "example": "Alice" + }, + "email": { + "type": "string", + "description": "email of the contact", + "example": "alice@acme.inc" + }, + "blocked": { + "type": "boolean", + "description": "whether the contact is blocked or not", + "example": false + }, + "phone_number": { + "type": "string", + "description": "phone number of the contact", + "example": "+123456789" + }, + "avatar": { + "type": "string", + "format": "binary", + "description": "Send the form data with the avatar image binary or use the avatar_url" + }, + "avatar_url": { + "type": "string", + "description": "The url to a jpeg, png file for the contact avatar", + "example": "https://example.com/avatar.png" + }, + "identifier": { + "type": "string", + "description": "A unique identifier for the contact in external system", + "example": "1234567890" + }, + "additional_attributes": { + "type": "object", + "description": "An object where you can store additional attributes for contact. example {\"type\":\"customer\", \"age\":30}", + "example": { + "type": "customer", + "age": 30 + } + }, + "custom_attributes": { + "type": "object", + "description": "An object where you can store custom attributes for contact. example {\"type\":\"customer\", \"age\":30}, this should have a valid custom attribute definition.", + "example": {} + } + } + }, + "contact_update_payload": { + "type": "object", + "properties": { + "name": { + "type": "string", + "description": "name of the contact", + "example": "Alice" + }, + "email": { + "type": "string", + "description": "email of the contact", + "example": "alice@acme.inc" + }, + "blocked": { + "type": "boolean", + "description": "whether the contact is blocked or not", + "example": false + }, + "phone_number": { + "type": "string", + "description": "phone number of the contact", + "example": "+123456789" + }, + "avatar": { + "type": "string", + "format": "binary", + "description": "Send the form data with the avatar image binary or use the avatar_url" + }, + "avatar_url": { + "type": "string", + "description": "The url to a jpeg, png file for the contact avatar", + "example": "https://example.com/avatar.png" + }, + "identifier": { + "type": "string", + "description": "A unique identifier for the contact in external system", + "example": "1234567890" + }, + "additional_attributes": { + "type": "object", + "description": "An object where you can store additional attributes for contact. example {\"type\":\"customer\", \"age\":30}", + "example": { + "type": "customer", + "age": 30 + } + }, + "custom_attributes": { + "type": "object", + "description": "An object where you can store custom attributes for contact. example {\"type\":\"customer\", \"age\":30}, this should have a valid custom attribute definition.", + "example": {} + } + } + }, + "conversation_create_payload": { + "type": "object", + "required": [ + "source_id", + "inbox_id" + ], + "properties": { + "source_id": { + "type": "string", + "description": "Conversation source id", + "example": "1234567890" + }, + "inbox_id": { + "type": "integer", + "description": "Id of inbox in which the conversation is created Allowed Inbox Types: Website, Phone, Api, Email", + "example": 1 + }, + "contact_id": { + "type": "integer", + "description": "Contact Id for which conversation is created", + "example": 1 + }, + "additional_attributes": { + "type": "object", + "description": "Lets you specify attributes like browser information", + "example": { + "browser": "Chrome", + "browser_version": "89.0.4389.82", + "os": "Windows", + "os_version": "10" + } + }, + "custom_attributes": { + "type": "object", + "description": "The object to save custom attributes for conversation, accepts custom attributes key and value", + "example": { + "attribute_key": "attribute_value", + "priority_conversation_number": 3 + } + }, + "status": { + "type": "string", + "enum": [ + "open", + "resolved", + "pending" + ], + "description": "Specify the conversation whether it's pending, open, closed", + "example": "open" + }, + "assignee_id": { + "type": "integer", + "description": "Agent Id for assigning a conversation to an agent", + "example": 1 + }, + "team_id": { + "type": "integer", + "description": "Team Id for assigning a conversation to a team\\", + "example": 1 + }, + "snoozed_until": { + "type": "string", + "format": "date-time", + "description": "Snoozed until date time", + "example": "2030-07-21T17:32:28Z" + }, + "message": { + "type": "object", + "description": "The initial message to be sent to the conversation", + "required": [ + "content" + ], + "properties": { + "content": { + "type": "string", + "description": "The content of the message", + "example": "Hello, how can I help you?" + }, + "template_params": { + "type": "object", + "description": "The template params for the message in case of whatsapp Channel", + "properties": { + "name": { + "type": "string", + "description": "Name of the template", + "example": "sample_issue_resolution" + }, + "category": { + "type": "string", + "description": "Category of the template", + "example": "UTILITY" + }, + "language": { + "type": "string", + "description": "Language of the template", + "example": "en_US" + }, + "processed_params": { + "type": "object", + "description": "The processed param values for template variables in template", + "example": { + "1": "Chatwoot" + } + } + } + } + } + } + } + }, + "conversation_message_create_payload": { + "type": "object", + "required": [ + "content" + ], + "properties": { + "content": { + "type": "string", + "description": "The content of the message", + "example": "Hello, how can I help you?" + }, + "message_type": { + "type": "string", + "enum": [ + "outgoing", + "incoming" + ], + "description": "The type of the message", + "example": "outgoing" + }, + "private": { + "type": "boolean", + "description": "Flag to identify if it is a private note", + "example": false + }, + "content_type": { + "type": "string", + "enum": [ + "text", + "input_email", + "cards", + "input_select", + "form", + "article" + ], + "description": "Content type of the message", + "example": "text" + }, + "content_attributes": { + "type": "object", + "description": "Attributes based on the content type", + "example": {} + }, + "campaign_id": { + "type": "integer", + "description": "The campaign id to which the message belongs", + "example": 1 + }, + "template_params": { + "type": "object", + "description": "The template params for the message in case of whatsapp Channel", + "properties": { + "name": { + "type": "string", + "description": "Name of the template", + "example": "sample_issue_resolution" + }, + "category": { + "type": "string", + "description": "Category of the template", + "example": "UTILITY" + }, + "language": { + "type": "string", + "description": "Language of the template", + "example": "en_US" + }, + "processed_params": { + "type": "object", + "description": "The processed param values for template variables in template", + "example": { + "1": "Chatwoot" + } + } + } + } + } + }, + "inbox_create_payload": { + "type": "object", + "properties": { + "name": { + "type": "string", + "description": "The name of the inbox", + "example": "Support" + }, + "avatar": { + "type": "string", + "format": "binary", + "description": "Image file for avatar" + }, + "greeting_enabled": { + "type": "boolean", + "description": "Enable greeting message", + "example": true + }, + "greeting_message": { + "type": "string", + "description": "Greeting message to be displayed on the widget", + "example": "Hello, how can I help you?" + }, + "enable_email_collect": { + "type": "boolean", + "description": "Enable email collection", + "example": true + }, + "csat_survey_enabled": { + "type": "boolean", + "description": "Enable CSAT survey", + "example": true + }, + "enable_auto_assignment": { + "type": "boolean", + "description": "Enable Auto Assignment", + "example": true + }, + "working_hours_enabled": { + "type": "boolean", + "description": "Enable working hours", + "example": true + }, + "out_of_office_message": { + "type": "string", + "description": "Out of office message to be displayed on the widget", + "example": "We are currently out of office. Please leave a message and we will get back to you." + }, + "timezone": { + "type": "string", + "description": "Timezone of the inbox", + "example": "America/New_York" + }, + "allow_messages_after_resolved": { + "type": "boolean", + "description": "Allow messages after conversation is resolved", + "example": true + }, + "lock_to_single_conversation": { + "type": "boolean", + "description": "Lock to single conversation", + "example": true + }, + "portal_id": { + "type": "integer", + "description": "Id of the help center portal to attach to the inbox", + "example": 1 + }, + "sender_name_type": { + "type": "string", + "description": "Sender name type for the inbox", + "enum": [ + "friendly", + "professional" + ], + "example": "friendly" + }, + "business_name": { + "type": "string", + "description": "Business name for the inbox", + "example": "My Business" + }, + "channel": { + "type": "object", + "properties": { + "type": { + "type": "string", + "description": "Type of the channel", + "enum": [ + "web_widget", + "api", + "email", + "line", + "telegram", + "whatsapp", + "sms" + ], + "example": "web_widget" + }, + "website_url": { + "type": "string", + "description": "URL at which the widget will be loaded", + "example": "https://example.com" + }, + "welcome_title": { + "type": "string", + "description": "Welcome title to be displayed on the widget", + "example": "Welcome to our support" + }, + "welcome_tagline": { + "type": "string", + "description": "Welcome tagline to be displayed on the widget", + "example": "We are here to help you" + }, + "widget_color": { + "type": "string", + "description": "A Hex-color string used to customize the widget", + "example": "#FF5733" + } + } + } + } + }, + "inbox_update_payload": { + "type": "object", + "properties": { + "name": { + "type": "string", + "description": "The name of the inbox", + "example": "Support" + }, + "avatar": { + "type": "string", + "format": "binary", + "description": "Image file for avatar" + }, + "greeting_enabled": { + "type": "boolean", + "description": "Enable greeting message", + "example": true + }, + "greeting_message": { + "type": "string", + "description": "Greeting message to be displayed on the widget", + "example": "Hello, how can I help you?" + }, + "enable_email_collect": { + "type": "boolean", + "description": "Enable email collection", + "example": true + }, + "csat_survey_enabled": { + "type": "boolean", + "description": "Enable CSAT survey", + "example": true + }, + "enable_auto_assignment": { + "type": "boolean", + "description": "Enable Auto Assignment", + "example": true + }, + "working_hours_enabled": { + "type": "boolean", + "description": "Enable working hours", + "example": true + }, + "out_of_office_message": { + "type": "string", + "description": "Out of office message to be displayed on the widget", + "example": "We are currently out of office. Please leave a message and we will get back to you." + }, + "timezone": { + "type": "string", + "description": "Timezone of the inbox", + "example": "America/New_York" + }, + "allow_messages_after_resolved": { + "type": "boolean", + "description": "Allow messages after conversation is resolved", + "example": true + }, + "lock_to_single_conversation": { + "type": "boolean", + "description": "Lock to single conversation", + "example": true + }, + "portal_id": { + "type": "integer", + "description": "Id of the help center portal to attach to the inbox", + "example": 1 + }, + "sender_name_type": { + "type": "string", + "description": "Sender name type for the inbox", + "enum": [ + "friendly", + "professional" + ], + "example": "friendly" + }, + "business_name": { + "type": "string", + "description": "Business name for the inbox", + "example": "My Business" + }, + "channel": { + "type": "object", + "properties": { + "website_url": { + "type": "string", + "description": "URL at which the widget will be loaded", + "example": "https://example.com" + }, + "welcome_title": { + "type": "string", + "description": "Welcome title to be displayed on the widget", + "example": "Welcome to our support" + }, + "welcome_tagline": { + "type": "string", + "description": "Welcome tagline to be displayed on the widget", + "example": "We are here to help you" + }, + "widget_color": { + "type": "string", + "description": "A Hex-color string used to customize the widget", + "example": "#FF5733" + } + } + } + } + }, + "team_create_update_payload": { + "type": "object", + "properties": { + "name": { + "type": "string", + "description": "The name of the team", + "example": "Support Team" + }, + "description": { + "type": "string", + "description": "The description of the team", + "example": "This is a team of support agents" + }, + "allow_auto_assign": { + "type": "boolean", + "description": "If this setting is turned on, the system would automatically assign the conversation to an agent in the team while assigning the conversation to a team", + "example": true + } + } + }, + "custom_filter_create_update_payload": { + "type": "object", + "properties": { + "name": { + "type": "string", + "description": "The name of the custom filter", + "example": "My Custom Filter" + }, + "type": { + "type": "string", + "enum": [ + "conversation", + "contact", + "report" + ], + "description": "The description about the custom filter", + "example": "conversation" + }, + "query": { + "type": "object", + "description": "A query that needs to be saved as a custom filter", + "example": {} + } + } + }, + "webhook_create_update_payload": { + "type": "object", + "properties": { + "url": { + "type": "string", + "description": "The url where the events should be sent", + "example": "https://example.com/webhook" + }, + "subscriptions": { + "type": "array", + "items": { + "type": "string", + "enum": [ + "conversation_created", + "conversation_status_changed", + "conversation_updated", + "message_created", + "message_updated", + "contact_created", + "contact_updated", + "webwidget_triggered" + ] + }, + "description": "The events you want to subscribe to.", + "example": [ + "conversation_created", + "conversation_status_changed" + ] + } + } + }, + "integrations_hook_create_payload": { + "type": "object", + "properties": { + "app_id": { + "type": "integer", + "description": "The ID of app for which integration hook is being created", + "example": 1 + }, + "inbox_id": { + "type": "integer", + "description": "The inbox ID, if the hook is an inbox hook", + "example": 1 + }, + "status": { + "type": "integer", + "description": "The status of the integration (0 for inactive, 1 for active)", + "example": 1 + }, + "settings": { + "type": "object", + "description": "The settings required by the integration", + "example": {} + } + } + }, + "integrations_hook_update_payload": { + "type": "object", + "properties": { + "status": { + "type": "integer", + "description": "The status of the integration (0 for inactive, 1 for active)", + "example": 1 + }, + "settings": { + "type": "object", + "description": "The settings required by the integration", + "example": {} + } + } + }, + "automation_rule_create_update_payload": { + "type": "object", + "properties": { + "name": { + "type": "string", + "description": "Rule name", + "example": "Add label on message create event" + }, + "description": { + "type": "string", + "description": "The description about the automation and actions", + "example": "Add label support and sales on message create event if incoming message content contains text help" + }, + "event_name": { "type": "string", "enum": [ "conversation_created", - "conversation_status_changed", "conversation_updated", - "message_created", - "message_updated", - "webwidget_triggered" - ] + "message_created" + ], + "example": "message_created", + "description": "The event when you want to execute the automation actions" }, - "description": "The events you want to subscribe to." - } - } - }, - "integrations_hook_create_payload": { - "type": "object", - "properties": { - "app_id": { - "type": "string", - "description": "The ID of app for which integration hook is being created" - }, - "inbox_id": { - "type": "string", - "description": "The inbox ID, if the hook is an inbox hook" - }, - "settings": { - "type": "object", - "description": "The settings required by the integration" - } - } - }, - "integrations_hook_update_payload": { - "type": "object", - "properties": { - "settings": { - "type": "object", - "description": "The settings required by the integration" - } - } - }, - "automation_rule_create_update_payload": { - "type": "object", - "properties": { - "name": { - "type": "string", - "description": "Rule name", - "example": "Add label on message create event" - }, - "description": { - "type": "string", - "description": "The description about the automation and actions", - "example": "Add label support and sales on message create event if incoming message content contains text help" - }, - "event_name": { - "type": "string", - "enum": [ - "conversation_created", - "conversation_updated", - "message_created" - ], - "example": "message_created", - "description": "The event when you want to execute the automation actions" - }, - "active": { - "type": "boolean", - "description": "Enable/disable automation rule" - }, - "actions": { - "type": "array", - "description": "Array of actions which you want to perform when condition matches, e.g add label support if message contains content help.", - "items": { - "type": "object", - "example": { - "action_name": "add_label", - "action_params": [ - "support" - ] + "active": { + "type": "boolean", + "description": "Enable/disable automation rule" + }, + "actions": { + "type": "array", + "description": "Array of actions which you want to perform when condition matches, e.g add label support if message contains content help.", + "items": { + "type": "object", + "example": { + "action_name": "add_label", + "action_params": [ + "support" + ] + } } - } - }, - "conditions": { - "type": "array", - "description": "Array of conditions on which conversation filter would work, e.g message content contains text help.", - "items": { - "type": "object", - "example": { - "attribute_key": "content", - "filter_operator": "contains", - "query_operator": "nil", - "values": [ - "help" - ] + }, + "conditions": { + "type": "array", + "description": "Array of conditions on which conversation filter would work, e.g message content contains text help.", + "items": { + "type": "object", + "example": { + "attribute_key": "content", + "filter_operator": "contains", + "query_operator": "OR", + "values": [ + "help" + ] + } } } } - } - }, - "portal_create_update_payload": { - "type": "object", - "properties": { - "archived": { - "type": "boolean", - "description": "Status to check if portal is live" - }, - "color": { - "type": "string", - "description": "Header color for help-center", - "example": "add color HEX string, \"#fffff\"" - }, - "config": { - "type": "object", - "description": "Configuration about supporting locales", - "example": { - "allowed_locales": [ - "en", - "es" - ], - "default_locale": "en" - } - }, - "custom_domain": { - "type": "string", - "description": "Custom domain to display help center.", - "example": "https://chatwoot.help/." - }, - "header_text": { - "type": "string", - "description": "Help center header", - "example": "Handbook" - }, - "homepage_link": { - "type": "string", - "description": "link to main dashboard", - "example": "https://www.chatwoot.com/" - }, - "name": { - "type": "string", - "description": "Name for the portal" - }, - "slug": { - "type": "string", - "description": "Slug for the portal to display in link" - }, - "page_title": { - "type": "string", - "description": "Page title for the portal" - }, - "account_id": { - "type": "integer" - } - } - }, - "category_create_update_payload": { - "type": "object", - "properties": { - "description": { - "type": "string", - "description": "Category description" - }, - "locale": { - "type": "string", - "description": "Category locale", - "example": "en/es" - }, - "name": { - "type": "string", - "description": "Category name" - }, - "slug": { - "type": "string", - "description": "Category slug" - }, - "position": { - "type": "integer", - "description": "Category position in the portal list to sort" - }, - "portal_id": { - "type": "integer" - }, - "account_id": { - "type": "integer" - }, - "associated_category_id": { - "type": "integer", - "description": "To associate similar categories to each other, e.g same category of product documentation in different languages" - }, - "parent_category_id": { - "type": "integer", - "description": "To define parent category, e.g product documentation has multiple level features in sales category or in engineering category." - } - } - }, - "article_create_update_payload": { - "type": "object", - "properties": { - "content": { - "type": "string", - "description": "The text content." - }, - "meta": { - "type": "object", - "description": "Use for search", - "example": { - "tags": [ - "article_name" - ], - "title": "article title", - "description": "article description" - } - }, - "position": { - "type": "integer", - "description": "article position in category" - }, - "status": { - "type": "integer", - "example": [ - "draft", - "published", - "archived" - ] - }, - "title": { - "type": "string" - }, - "slug": { - "type": "string" - }, - "views": { - "type": "integer" - }, - "portal_id": { - "type": "integer" - }, - "account_id": { - "type": "integer" - }, - "author_id": { - "type": "integer" - }, - "category_id": { - "type": "integer" - }, - "folder_id": { - "type": "integer" - }, - "associated_article_id": { - "type": "integer", - "description": "To associate similar articles to each other, e.g to provide the link for the reference." - } - } - }, - "public_contact_create_update_payload": { - "type": "object", - "properties": { - "identifier": { - "type": "string", - "description": "External identifier of the contact" - }, - "identifier_hash": { - "type": "string", - "description": "Identifier hash prepared for HMAC authentication" - }, - "email": { - "type": "string", - "description": "Email of the contact" - }, - "name": { - "type": "string", - "description": "Name of the contact" - }, - "phone_number": { - "type": "string", - "description": "Phone number of the contact" - }, - "avatar_url": { - "type": "string", - "description": "The url to a jpeg, png file for the user avatar" - }, - "custom_attributes": { - "type": "object", - "description": "Custom attributes of the customer" - } - } - }, - "public_message_create_payload": { - "type": "object", - "properties": { - "content": { - "type": "string", - "description": "Content for the message" - }, - "echo_id": { - "type": "string", - "description": "Temporary identifier which will be passed back via websockets" - } - } - }, - "public_message_update_payload": { - "type": "object", - "properties": { - "submitted_values": { - "type": "object", - "description": "Replies to the Bot Message Types" - } - } - }, - "public_conversation_create_payload": { - "type": "object", - "properties": { - "custom_attributes": { - "type": "object", - "description": "Custom attributes of the conversation" - } - } - }, - "extended_contact": { - "allOf": [ - { - "$ref": "#/definitions/contact" - }, - { - "type": "object", - "properties": { - "id": { - "type": "number", - "description": "Id of the user" - }, - "availability_status": { - "type": "string", - "enum": [ - "online", - "offline" + }, + "portal_create_update_payload": { + "type": "object", + "properties": { + "color": { + "type": "string", + "description": "Header color for help-center in hex format", + "example": "#FFFFFF" + }, + "custom_domain": { + "type": "string", + "description": "Custom domain to display help center.", + "example": "chatwoot.help" + }, + "header_text": { + "type": "string", + "description": "Help center header", + "example": "Handbook" + }, + "homepage_link": { + "type": "string", + "description": "link to main dashboard", + "example": "https://www.chatwoot.com/" + }, + "name": { + "type": "string", + "description": "Name for the portal", + "example": "Handbook" + }, + "page_title": { + "type": "string", + "description": "Page title for the portal", + "example": "Handbook" + }, + "slug": { + "type": "string", + "description": "Slug for the portal to display in link", + "example": "handbook" + }, + "archived": { + "type": "boolean", + "description": "Status to check if portal is live", + "example": false + }, + "config": { + "type": "object", + "description": "Configuration about supporting locales", + "example": { + "allowed_locales": [ + "en", + "es" ], - "description": "Availability status of the user" + "default_locale": "en" } } } - ] - }, - "contact_base": { - "allOf": [ - { - "$ref": "#/definitions/generic_id" - }, - { - "$ref": "#/definitions/contact" + }, + "category_create_update_payload": { + "type": "object", + "properties": { + "name": { + "type": "string", + "description": "The name of the category", + "example": "Category Name" + }, + "description": { + "type": "string", + "description": "A description for the category", + "example": "Category description" + }, + "position": { + "type": "integer", + "description": "Category position in the portal list to sort", + "example": 1 + }, + "slug": { + "type": "string", + "description": "The category slug used in the URL", + "example": "category-name" + }, + "locale": { + "type": "string", + "description": "The locale of the category", + "example": "en" + }, + "icon": { + "type": "string", + "description": "The icon of the category as a string (emoji)", + "example": "📚" + }, + "parent_category_id": { + "type": "integer", + "description": "To define parent category, e.g product documentation has multiple level features in sales category or in engineering category.", + "example": 1 + }, + "associated_category_id": { + "type": "integer", + "description": "To associate similar categories to each other, e.g same category of product documentation in different languages", + "example": 2 + } } - ] - }, - "contact_list": { - "type": "array", - "description": "array of contacts", - "items": { + }, + "article_create_update_payload": { + "type": "object", + "properties": { + "title": { + "type": "string", + "description": "The title of the article", + "example": "Article Title" + }, + "slug": { + "type": "string", + "description": "The slug of the article", + "example": "article-title" + }, + "position": { + "type": "integer", + "description": "article position in category", + "example": 1 + }, + "content": { + "type": "string", + "description": "The text content.", + "example": "This is the content of the article" + }, + "description": { + "type": "string", + "description": "The description of the article", + "example": "This is the description of the article" + }, + "category_id": { + "type": "integer", + "description": "The category id of the article", + "example": 1 + }, + "author_id": { + "type": "integer", + "description": "The author agent id of the article", + "example": 1 + }, + "associated_article_id": { + "type": "integer", + "description": "To associate similar articles to each other, e.g to provide the link for the reference.", + "example": 2 + }, + "status": { + "type": "integer", + "description": "The status of the article. 0 for draft, 1 for published, 2 for archived", + "example": 1 + }, + "locale": { + "type": "string", + "description": "The locale of the article", + "example": "en" + }, + "meta": { + "type": "object", + "description": "Use for search", + "example": { + "tags": [ + "article_name" + ], + "title": "article title", + "description": "article description" + } + } + } + }, + "public_contact_create_update_payload": { + "type": "object", + "properties": { + "identifier": { + "type": "string", + "description": "External identifier of the contact", + "example": "1234567890" + }, + "identifier_hash": { + "type": "string", + "description": "Identifier hash prepared for HMAC authentication", + "example": "e93275d4eba0e5679ad55f5360af00444e2a888df9b0afa3e8b691c3173725f9" + }, + "email": { + "type": "string", + "description": "Email of the contact", + "example": "alice@acme.inc" + }, + "name": { + "type": "string", + "description": "Name of the contact", + "example": "Alice" + }, + "phone_number": { + "type": "string", + "description": "Phone number of the contact", + "example": "+123456789" + }, + "avatar": { + "type": "string", + "format": "binary", + "description": "Send the form data with the avatar image binary or use the avatar_url" + }, + "custom_attributes": { + "type": "object", + "description": "Custom attributes of the customer", + "example": {} + } + } + }, + "public_message_create_payload": { + "type": "object", + "properties": { + "content": { + "type": "string", + "description": "Content for the message", + "example": "Hello, how can I help you?" + }, + "echo_id": { + "type": "string", + "description": "Temporary identifier which will be passed back via websockets", + "example": "1234567890" + } + } + }, + "public_message_update_payload": { + "type": "object", + "properties": { + "submitted_values": { + "type": "object", + "description": "Replies to the Bot Message Types", + "properties": { + "name": { + "type": "string", + "description": "The name of the submiitted value", + "example": "My Name" + }, + "title": { + "type": "string", + "description": "The title of the submitted value", + "example": "My Title" + }, + "value": { + "type": "string", + "description": "The value of the submitted value", + "example": "value" + }, + "csat_survey_response": { + "type": "object", + "description": "The CSAT survey response", + "properties": { + "feedback_message": { + "type": "string", + "description": "The feedback message of the CSAT survey response", + "example": "Great service!" + }, + "rating": { + "type": "integer", + "description": "The rating of the CSAT survey response", + "example": 5 + } + } + } + } + } + } + }, + "public_conversation_create_payload": { + "type": "object", + "properties": { + "custom_attributes": { + "type": "object", + "description": "Custom attributes of the conversation", + "example": {} + } + } + }, + "extended_contact": { "allOf": [ { - "$ref": "#/definitions/generic_id" + "$ref": "#/components/schemas/contact" }, { - "$ref": "#/definitions/contact" + "type": "object", + "properties": { + "id": { + "type": "number", + "description": "Id of the user" + }, + "availability_status": { + "type": "string", + "enum": [ + "online", + "offline" + ], + "description": "Availability status of the user" + } + } } ] - } - }, - "contact_conversations": { - "type": "array", - "description": "array of conversations", - "items": { + }, + "contact_base": { "allOf": [ { - "$ref": "#/definitions/conversation" + "$ref": "#/components/schemas/generic_id" + }, + { + "$ref": "#/components/schemas/contact" + } + ] + }, + "contact_list": { + "type": "array", + "description": "array of contacts", + "items": { + "allOf": [ + { + "$ref": "#/components/schemas/contact" + } + ] + } + }, + "contact_conversations": { + "type": "array", + "description": "array of conversations", + "items": { + "allOf": [ + { + "$ref": "#/components/schemas/conversation" + }, + { + "type": "object", + "properties": { + "meta": { + "type": "object", + "properties": { + "sender": { + "type": "object", + "properties": { + "additional_attributes": { + "type": "object", + "description": "The additional attributes of the sender" + }, + "availability_status": { + "type": "string", + "description": "The availability status of the sender" + }, + "email": { + "type": "string", + "description": "The email of the sender" + }, + "id": { + "type": "number", + "description": "ID fo the sender" + }, + "name": { + "type": "string", + "description": "The name of the sender" + }, + "phone_number": { + "type": "string", + "description": "The phone number of the sender" + }, + "blocked": { + "type": "boolean", + "description": "Whether the sender is blocked" + }, + "identifier": { + "type": "string", + "description": "The identifier of the sender" + }, + "thumbnail": { + "type": "string", + "description": "Avatar URL of the contact" + }, + "custom_attributes": { + "type": "object", + "description": "The custom attributes of the sender" + }, + "last_activity_at": { + "type": "number", + "description": "The last activity at of the sender" + }, + "created_at": { + "type": "number", + "description": "The created at of the sender" + } + } + }, + "channel": { + "type": "string", + "description": "Channel Type" + }, + "assignee": { + "$ref": "#/components/schemas/user" + }, + "hmac_verified": { + "type": "boolean", + "description": "Whether the hmac is verified" + } + } + } + } + }, + { + "type": "object", + "properties": { + "display_id": { + "type": "number" + } + } + } + ] + } + }, + "contact_labels": { + "type": "object", + "properties": { + "payload": { + "type": "array", + "description": "Array of labels", + "items": { + "type": "string" + } + } + } + }, + "conversation_list": { + "type": "object", + "properties": { + "data": { + "type": "object", + "properties": { + "meta": { + "type": "object", + "properties": { + "mine_count": { + "type": "number" + }, + "unassigned_count": { + "type": "number" + }, + "assigned_count": { + "type": "number" + }, + "all_count": { + "type": "number" + } + } + }, + "payload": { + "type": "array", + "description": "array of conversations", + "items": { + "allOf": [ + { + "$ref": "#/components/schemas/generic_id" + }, + { + "$ref": "#/components/schemas/conversation" + }, + { + "type": "object", + "properties": { + "meta": { + "type": "object", + "properties": { + "sender": { + "type": "object", + "properties": { + "additional_attributes": { + "type": "object", + "description": "The additional attributes of the sender" + }, + "availability_status": { + "type": "string", + "description": "The availability status of the sender" + }, + "email": { + "type": "string", + "description": "The email of the sender" + }, + "id": { + "type": "number", + "description": "ID fo the sender" + }, + "name": { + "type": "string", + "description": "The name of the sender" + }, + "phone_number": { + "type": "string", + "description": "The phone number of the sender" + }, + "blocked": { + "type": "boolean", + "description": "Whether the sender is blocked" + }, + "identifier": { + "type": "string", + "description": "The identifier of the sender" + }, + "thumbnail": { + "type": "string", + "description": "Avatar URL of the contact" + }, + "custom_attributes": { + "type": "object", + "description": "The custom attributes of the sender" + }, + "last_activity_at": { + "type": "number", + "description": "The last activity at of the sender" + }, + "created_at": { + "type": "number", + "description": "The created at of the sender" + } + } + }, + "channel": { + "type": "string", + "description": "Channel Type" + }, + "assignee": { + "$ref": "#/components/schemas/user" + }, + "hmac_verified": { + "type": "boolean", + "description": "Whether the hmac is verified" + } + } + } + } + } + ] + } + } + } + } + } + }, + "conversation_show": { + "type": "object", + "allOf": [ + { + "$ref": "#/components/schemas/conversation" }, { "type": "object", @@ -7114,6 +10640,18 @@ "sender": { "type": "object", "properties": { + "additional_attributes": { + "type": "object", + "description": "The additional attributes of the sender" + }, + "availability_status": { + "type": "string", + "description": "The availability status of the sender" + }, + "email": { + "type": "string", + "description": "The email of the sender" + }, "id": { "type": "number", "description": "ID fo the sender" @@ -7122,440 +10660,1149 @@ "type": "string", "description": "The name of the sender" }, + "phone_number": { + "type": "string", + "description": "The phone number of the sender" + }, + "blocked": { + "type": "boolean", + "description": "Whether the sender is blocked" + }, + "identifier": { + "type": "string", + "description": "The identifier of the sender" + }, "thumbnail": { "type": "string", "description": "Avatar URL of the contact" }, - "channel": { - "type": "string", - "description": "Channel Type" + "custom_attributes": { + "type": "object", + "description": "The custom attributes of the sender" + }, + "last_activity_at": { + "type": "number", + "description": "The last activity at of the sender" + }, + "created_at": { + "type": "number", + "description": "The created at of the sender" } } }, + "channel": { + "type": "string", + "description": "Channel Type" + }, "assignee": { - "$ref": "#/definitions/user" + "$ref": "#/components/schemas/user" + }, + "hmac_verified": { + "type": "boolean", + "description": "Whether the hmac is verified" + } + } + } + } + } + ] + }, + "conversation_status_toggle": { + "type": "object", + "properties": { + "meta": { + "type": "object" + }, + "payload": { + "type": "object", + "properties": { + "success": { + "type": "boolean" + }, + "current_status": { + "type": "string", + "enum": [ + "open", + "resolved" + ] + }, + "conversation_id": { + "type": "number" + } + } + } + } + }, + "conversation_labels": { + "type": "object", + "properties": { + "payload": { + "type": "array", + "description": "Array of labels", + "items": { + "type": "string" + } + } + } + }, + "account_summary": { + "type": "object", + "properties": { + "avg_first_response_time": { + "type": "string" + }, + "avg_resolution_time": { + "type": "string" + }, + "conversations_count": { + "type": "number" + }, + "incoming_messages_count": { + "type": "number" + }, + "outgoing_messages_count": { + "type": "number" + }, + "resolutions_count": { + "type": "number" + }, + "previous": { + "type": "object", + "properties": { + "avg_first_response_time": { + "type": "string" + }, + "avg_resolution_time": { + "type": "string" + }, + "conversations_count": { + "type": "number" + }, + "incoming_messages_count": { + "type": "number" + }, + "outgoing_messages_count": { + "type": "number" + }, + "resolutions_count": { + "type": "number" + } + } + } + } + }, + "agent_conversation_metrics": { + "type": "object", + "properties": { + "id": { + "type": "number" + }, + "name": { + "type": "string" + }, + "email": { + "type": "string" + }, + "thumbnail": { + "type": "string" + }, + "availability": { + "type": "string" + }, + "metric": { + "type": "object", + "properties": { + "open": { + "type": "number" + }, + "unattended": { + "type": "number" + } + } + } + } + }, + "contact_detail": { + "type": "object", + "properties": { + "additional_attributes": { + "type": "object", + "description": "The object containing additional attributes related to the contact", + "properties": { + "city": { + "type": "string", + "description": "City of the contact" + }, + "country": { + "type": "string", + "description": "Country of the contact" + }, + "country_code": { + "type": "string", + "description": "Country code of the contact" + }, + "created_at_ip": { + "type": "string", + "description": "IP address when the contact was created" + } + } + }, + "custom_attributes": { + "type": "object", + "description": "The custom attributes of the contact" + }, + "email": { + "type": "string", + "description": "The email address of the contact" + }, + "id": { + "type": "integer", + "description": "The ID of the contact" + }, + "identifier": { + "type": "string", + "description": "The identifier of the contact", + "nullable": true + }, + "name": { + "type": "string", + "description": "The name of the contact" + }, + "phone_number": { + "type": "string", + "description": "The phone number of the contact", + "nullable": true + }, + "thumbnail": { + "type": "string", + "description": "The thumbnail of the contact" + }, + "blocked": { + "type": "boolean", + "description": "Whether the contact is blocked" + }, + "type": { + "type": "string", + "description": "The type of entity", + "enum": [ + "contact" + ] + } + } + }, + "message_detailed": { + "type": "object", + "properties": { + "id": { + "type": "number", + "description": "The ID of the message" + }, + "content": { + "type": "string", + "description": "The text content of the message" + }, + "inbox_id": { + "type": "number", + "description": "The ID of the inbox" + }, + "conversation_id": { + "type": "number", + "description": "The ID of the conversation" + }, + "message_type": { + "type": "integer", + "enum": [ + 0, + 1, + 2, + 3 + ], + "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" + ], + "description": "The type of the message content" + }, + "status": { + "type": "string", + "enum": [ + "sent", + "delivered", + "read", + "failed" + ], + "description": "The status of the message" + }, + "content_attributes": { + "type": "object", + "description": "The content attributes for each content_type", + "properties": { + "in_reply_to": { + "type": "string", + "description": "ID of the message this is replying to", + "nullable": true + } + } + }, + "created_at": { + "type": "integer", + "description": "The timestamp when message was created" + }, + "private": { + "type": "boolean", + "description": "The flag which shows whether the message is private or not" + }, + "source_id": { + "type": "string", + "description": "The source ID of the message", + "nullable": true + }, + "sender": { + "$ref": "#/components/schemas/contact_detail" + } + } + }, + "conversation_meta": { + "type": "object", + "properties": { + "labels": { + "type": "array", + "items": { + "type": "string" + }, + "description": "Labels associated with the conversation" + }, + "additional_attributes": { + "type": "object", + "properties": { + "browser": { + "type": "object", + "properties": { + "device_name": { + "type": "string", + "description": "Name of the device" + }, + "browser_name": { + "type": "string", + "description": "Name of the browser" + }, + "platform_name": { + "type": "string", + "description": "Name of the platform" + }, + "browser_version": { + "type": "string", + "description": "Version of the browser" + }, + "platform_version": { + "type": "string", + "description": "Version of the platform" + } + } + }, + "referer": { + "type": "string", + "description": "Referrer URL" + }, + "initiated_at": { + "type": "object", + "properties": { + "timestamp": { + "type": "string", + "description": "Timestamp when the conversation was initiated" + } + } + }, + "browser_language": { + "type": "string", + "description": "Browser language setting" + }, + "conversation_language": { + "type": "string", + "description": "Conversation language" + } + }, + "description": "Additional attributes of the conversation" + }, + "contact": { + "$ref": "#/components/schemas/contact_detail" + }, + "agent_last_seen_at": { + "type": "string", + "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 + } + } + }, + "conversation_messages": { + "type": "object", + "properties": { + "meta": { + "$ref": "#/components/schemas/conversation_meta" + }, + "payload": { + "type": "array", + "items": { + "$ref": "#/components/schemas/message_detailed" + }, + "description": "List of messages in the conversation" + } + } + }, + "contact_meta": { + "type": "object", + "properties": { + "count": { + "type": "integer", + "description": "Total number of contacts" + }, + "current_page": { + "type": "string", + "description": "Current page number" + } + } + }, + "contact_inbox": { + "type": "object", + "properties": { + "source_id": { + "type": "string", + "description": "Source identifier for the contact inbox" + }, + "inbox": { + "type": "object", + "properties": { + "id": { + "type": "integer", + "description": "ID of the inbox" + }, + "avatar_url": { + "type": "string", + "description": "URL for the inbox avatar" + }, + "channel_id": { + "type": "integer", + "description": "ID of the channel" + }, + "name": { + "type": "string", + "description": "Name of the inbox" + }, + "channel_type": { + "type": "string", + "description": "Type of channel" + }, + "provider": { + "type": "string", + "description": "Provider of the inbox", + "nullable": true + } + } + } + } + }, + "contact_list_item": { + "type": "object", + "properties": { + "additional_attributes": { + "type": "object", + "description": "The object containing additional attributes related to the contact", + "properties": { + "city": { + "type": "string", + "description": "City of the contact" + }, + "country": { + "type": "string", + "description": "Country of the contact" + }, + "country_code": { + "type": "string", + "description": "Country code of the contact" + }, + "created_at_ip": { + "type": "string", + "description": "IP address when the contact was created" + } + } + }, + "availability_status": { + "type": "string", + "description": "Availability status of the contact", + "enum": [ + "online", + "offline" + ] + }, + "email": { + "type": "string", + "description": "The email address of the contact", + "nullable": true + }, + "id": { + "type": "integer", + "description": "The ID of the contact" + }, + "name": { + "type": "string", + "description": "The name of the contact" + }, + "phone_number": { + "type": "string", + "description": "The phone number of the contact", + "nullable": true + }, + "blocked": { + "type": "boolean", + "description": "Whether the contact is blocked" + }, + "identifier": { + "type": "string", + "description": "The identifier of the contact", + "nullable": true + }, + "thumbnail": { + "type": "string", + "description": "The thumbnail of the contact" + }, + "custom_attributes": { + "type": "object", + "description": "The custom attributes of the contact" + }, + "last_activity_at": { + "type": "integer", + "description": "Timestamp of last activity", + "nullable": true + }, + "created_at": { + "type": "integer", + "description": "Timestamp when contact was created" + }, + "contact_inboxes": { + "type": "array", + "description": "List of inboxes associated with this contact", + "items": { + "$ref": "#/components/schemas/contact_inbox" + } + } + } + }, + "contacts_list_response": { + "type": "object", + "properties": { + "meta": { + "$ref": "#/components/schemas/contact_meta" + }, + "payload": { + "type": "array", + "items": { + "$ref": "#/components/schemas/contact_list_item" + }, + "description": "List of contacts" + } + } + }, + "contact_show_response": { + "type": "object", + "properties": { + "payload": { + "$ref": "#/components/schemas/contact_list_item" + } + } + }, + "contact_conversation_message": { + "type": "object", + "properties": { + "id": { + "type": "integer", + "description": "ID of the message" + }, + "content": { + "type": "string", + "description": "Content of the message" + }, + "account_id": { + "type": "integer", + "description": "ID of the account" + }, + "inbox_id": { + "type": "integer", + "description": "ID of the inbox" + }, + "conversation_id": { + "type": "integer", + "description": "ID of the conversation" + }, + "message_type": { + "type": "integer", + "description": "Type of the message" + }, + "created_at": { + "type": "integer", + "description": "Timestamp when message was created" + }, + "updated_at": { + "type": "string", + "description": "Formatted datetime when message was updated" + }, + "private": { + "type": "boolean", + "description": "Whether the message is private" + }, + "status": { + "type": "string", + "description": "Status of the message" + }, + "source_id": { + "type": "string", + "description": "Source ID of the message", + "nullable": true + }, + "content_type": { + "type": "string", + "description": "Type of the content" + }, + "content_attributes": { + "type": "object", + "description": "Attributes of the content" + }, + "sender_type": { + "type": "string", + "description": "Type of the sender", + "nullable": true + }, + "sender_id": { + "type": "integer", + "description": "ID of the sender", + "nullable": true + }, + "external_source_ids": { + "type": "object", + "description": "External source IDs" + }, + "additional_attributes": { + "type": "object", + "description": "Additional attributes of the message" + }, + "processed_message_content": { + "type": "string", + "description": "Processed message content", + "nullable": true + }, + "sentiment": { + "type": "object", + "description": "Sentiment analysis of the message" + }, + "conversation": { + "type": "object", + "description": "Conversation details", + "properties": { + "assignee_id": { + "type": "integer", + "description": "ID of the assignee", + "nullable": true + }, + "unread_count": { + "type": "integer", + "description": "Count of unread messages" + }, + "last_activity_at": { + "type": "integer", + "description": "Timestamp of last activity" + }, + "contact_inbox": { + "type": "object", + "description": "Contact inbox details", + "properties": { + "source_id": { + "type": "string", + "description": "Source ID of the contact inbox" } } } } }, - { + "sender": { "type": "object", + "description": "Details of the sender", "properties": { - "display_id": { - "type": "number" + "id": { + "type": "integer", + "description": "ID of the sender" + }, + "name": { + "type": "string", + "description": "Name of the sender" + }, + "available_name": { + "type": "string", + "description": "Available name of the sender" + }, + "avatar_url": { + "type": "string", + "description": "URL of the sender's avatar" + }, + "type": { + "type": "string", + "description": "Type of the sender" + }, + "availability_status": { + "type": "string", + "description": "Availability status of the sender" + }, + "thumbnail": { + "type": "string", + "description": "Thumbnail URL of the sender" } } } - ] - } - }, - "contact_labels": { - "type": "object", - "properties": { - "payload": { - "type": "array", - "description": "Array of labels", - "items": { - "type": "string" - } } - } - }, - "conversation_list": { - "type": "object", - "properties": { - "data": { - "type": "object", - "properties": { - "meta": { - "type": "object", - "properties": { - "mine_count": { - "type": "number" + }, + "contact_conversations_response": { + "type": "object", + "properties": { + "payload": { + "type": "array", + "items": { + "allOf": [ + { + "$ref": "#/components/schemas/conversation" }, - "unassigned_count": { - "type": "number" - }, - "assigned_count": { - "type": "number" - }, - "all_count": { - "type": "number" - } - } - }, - "payload": { - "type": "array", - "description": "array of conversations", - "items": { - "allOf": [ - { - "$ref": "#/definitions/generic_id" - }, - { - "$ref": "#/definitions/conversation" - }, - { - "type": "object", - "properties": { - "meta": { - "type": "object", - "properties": { - "sender": { - "type": "object", - "properties": { - "id": { - "type": "number", - "description": "ID fo the sender" - }, - "name": { - "type": "string", - "description": "The name of the sender" - }, - "thumbnail": { - "type": "string", - "description": "Avatar URL of the contact" - }, - "channel": { - "type": "string", - "description": "Channel Type" - } + { + "type": "object", + "properties": { + "meta": { + "type": "object", + "properties": { + "sender": { + "type": "object", + "properties": { + "additional_attributes": { + "type": "object", + "description": "The additional attributes of the sender" + }, + "availability_status": { + "type": "string", + "description": "The availability status of the sender" + }, + "email": { + "type": "string", + "description": "The email of the sender" + }, + "id": { + "type": "number", + "description": "ID fo the sender" + }, + "name": { + "type": "string", + "description": "The name of the sender" + }, + "phone_number": { + "type": "string", + "description": "The phone number of the sender" + }, + "blocked": { + "type": "boolean", + "description": "Whether the sender is blocked" + }, + "identifier": { + "type": "string", + "description": "The identifier of the sender" + }, + "thumbnail": { + "type": "string", + "description": "Avatar URL of the contact" + }, + "custom_attributes": { + "type": "object", + "description": "The custom attributes of the sender" + }, + "last_activity_at": { + "type": "number", + "description": "The last activity at of the sender" + }, + "created_at": { + "type": "number", + "description": "The created at of the sender" } - }, - "assignee": { - "$ref": "#/definitions/user" } + }, + "channel": { + "type": "string", + "description": "Channel Type" + }, + "assignee": { + "$ref": "#/components/schemas/user" + }, + "hmac_verified": { + "type": "boolean", + "description": "Whether the hmac is verified" } } } } - ] - } - } - } - } - } - }, - "conversation_show": { - "type": "object", - "allOf": [ - { - "$ref": "#/definitions/conversation" - }, - { - "type": "object", - "properties": { - "meta": { - "type": "object", - "properties": { - "sender": { - "type": "object", - "properties": { - "id": { - "type": "number", - "description": "ID fo the sender" - }, - "name": { - "type": "string", - "description": "The name of the sender" - }, - "thumbnail": { - "type": "string", - "description": "Avatar URL of the contact" - }, - "channel": { - "type": "string", - "description": "Channel Type" - } - } - }, - "assignee": { - "$ref": "#/definitions/user" } - } - } - } - } - ] - }, - "conversation_status_toggle": { - "type": "object", - "properties": { - "meta": { - "type": "object" - }, - "payload": { - "type": "object", - "properties": { - "success": { - "type": "boolean" - }, - "current_status": { - "type": "string", - "enum": [ - "open", - "resolved" ] }, - "conversation_id": { - "type": "number" - } + "description": "List of conversations for the contact" + } + } + }, + "contactable_inboxes_response": { + "type": "object", + "properties": { + "payload": { + "type": "array", + "items": { + "$ref": "#/components/schemas/contact_inbox" + }, + "description": "List of contactable inboxes for the contact" } } } }, - "conversation_labels": { - "type": "object", - "properties": { - "payload": { - "type": "array", - "description": "Array of labels", - "items": { - "type": "string" - } - } + "parameters": { + "account_id": { + "in": "path", + "name": "account_id", + "schema": { + "type": "integer" + }, + "required": true, + "description": "The numeric ID of the account" + }, + "agent_bot_id": { + "in": "path", + "name": "id", + "schema": { + "type": "integer" + }, + "required": true, + "description": "The ID of the agentbot to be updated" + }, + "team_id": { + "in": "path", + "name": "team_id", + "schema": { + "type": "integer" + }, + "required": true, + "description": "The ID of the team to be updated" + }, + "inbox_id": { + "in": "path", + "name": "inbox_id", + "schema": { + "type": "integer" + }, + "required": true, + "description": "The ID of the Inbox" + }, + "hook_id": { + "in": "path", + "name": "hook_id", + "schema": { + "type": "integer" + }, + "required": true, + "description": "The numeric ID of the integration hook" + }, + "source_id": { + "in": "path", + "name": "source_id", + "required": true, + "schema": { + "type": "string" + }, + "description": "Id of the session for which the conversation is created.\n\n\n\n Source Ids can be obtained through contactable inboxes API or via generated.Website: Chatwoot generated string which can be obtained from webhook events. Phone Channels(Twilio): Phone number in e164 format Email Channels: Contact Email address API Channel: Any Random String" + }, + "contact_sort_param": { + "in": "query", + "name": "sort", + "schema": { + "type": "string", + "enum": [ + "name", + "email", + "phone_number", + "last_activity_at", + "-name", + "-email", + "-phone_number", + "-last_activity_at" + ] + }, + "required": false, + "description": "The attribute by which list should be sorted" + }, + "conversation_id": { + "in": "path", + "name": "conversation_id", + "schema": { + "type": "integer" + }, + "required": true, + "description": "The numeric ID of the conversation" + }, + "conversation_uuid": { + "in": "path", + "name": "conversation_uuid", + "schema": { + "type": "integer" + }, + "required": true, + "description": "The uuid of the conversation" + }, + "custom_filter_id": { + "in": "path", + "name": "custom_filter_id", + "schema": { + "type": "integer" + }, + "required": true, + "description": "The numeric ID of the custom filter" + }, + "webhook_id": { + "in": "path", + "name": "webhook_id", + "schema": { + "type": "integer" + }, + "required": true, + "description": "The numeric ID of the webhook" + }, + "message_id": { + "in": "path", + "name": "message_id", + "schema": { + "type": "integer" + }, + "required": true, + "description": "The numeric ID of the message" + }, + "page": { + "in": "query", + "name": "page", + "schema": { + "type": "integer", + "default": 1 + }, + "required": false, + "description": "The page parameter" + }, + "platform_user_id": { + "in": "path", + "name": "id", + "schema": { + "type": "integer" + }, + "required": true, + "description": "The numeric ID of the user on the platform" + }, + "report_type": { + "in": "query", + "name": "type", + "schema": { + "type": "string", + "enum": [ + "account", + "agent", + "inbox", + "label", + "team" + ] + }, + "required": true, + "description": "Type of report" + }, + "report_metric": { + "in": "query", + "name": "metric", + "schema": { + "type": "string", + "enum": [ + "conversations_count", + "incoming_messages_count", + "outgoing_messages_count", + "avg_first_response_time", + "avg_resolution_time", + "resolutions_count" + ] + }, + "required": true, + "description": "The type of metric" + }, + "public_inbox_identifier": { + "in": "path", + "name": "inbox_identifier", + "schema": { + "type": "string" + }, + "required": true, + "description": "The identifier obtained from API inbox channel" + }, + "public_contact_identifier": { + "in": "path", + "name": "contact_identifier", + "schema": { + "type": "string" + }, + "required": true, + "description": "The source id of contact obtained on contact create" + }, + "portal_id": { + "in": "path", + "name": "id", + "schema": { + "type": "string" + }, + "required": true, + "description": "The slug identifier of the portal" } }, - "account_summary": { - "type": "object", - "properties": { - "avg_first_response_time": { - "type": "string" - }, - "avg_resolution_time": { - "type": "string" - }, - "conversations_count": { - "type": "number" - }, - "incoming_messages_count": { - "type": "number" - }, - "outgoing_messages_count": { - "type": "number" - }, - "resolutions_count": { - "type": "number" - }, - "previous": { - "type": "object", - "properties": { - "avg_first_response_time": { - "type": "string" - }, - "avg_resolution_time": { - "type": "string" - }, - "conversations_count": { - "type": "number" - }, - "incoming_messages_count": { - "type": "number" - }, - "outgoing_messages_count": { - "type": "number" - }, - "resolutions_count": { - "type": "number" - } - } - } - } - }, - "agent_conversation_metrics": { - "type": "object", - "properties": { - "id": { - "type": "number" - }, - "name": { - "type": "string" - }, - "email": { - "type": "string" - }, - "thumbnail": { - "type": "string" - }, - "availability": { - "type": "string" - }, - "metric": { - "type": "object", - "properties": { - "open": { - "type": "number" - }, - "unattended": { - "type": "number" - } - } - } + "securitySchemes": { + "userApiKey": { + "type": "apiKey", + "in": "header", + "name": "api_access_token", + "description": "This token can be obtained by visiting the profile page or via rails console. Provides access to endpoints based on the user permissions levels. This token can be saved by an external system when user is created via API, to perform activities on behalf of the user." + }, + "agentBotApiKey": { + "type": "apiKey", + "in": "header", + "name": "api_access_token", + "description": "This token should be provided by system admin or obtained via rails console. This token can be used to build bot integrations and can only access limited apis." + }, + "platformAppApiKey": { + "type": "apiKey", + "in": "header", + "name": "api_access_token", + "description": "This token can be obtained by the system admin after creating a platformApp. This token should be used to provision agent bots, accounts, users and their roles." } } }, - "parameters": { - "account_id": { - "in": "path", - "name": "account_id", - "type": "integer", - "required": true, - "description": "The numeric ID of the account" + "tags": [ + { + "name": "Accounts", + "description": "Account management APIs" }, - "agent_bot_id": { - "in": "path", - "name": "id", - "type": "integer", - "required": true, - "description": "The ID of the agentbot to be updated" + { + "name": "Account Users", + "description": "Account user management APIs" }, - "team_id": { - "in": "path", - "name": "team_id", - "type": "integer", - "required": true, - "description": "The ID of the team to be updated" + { + "name": "AgentBots", + "description": "Bot integrations" }, - "inbox_id": { - "in": "path", - "name": "inbox_id", - "type": "integer", - "required": true, - "description": "The ID of the Inbox" + { + "name": "Users", + "description": "User management APIs" }, - "hook_id": { - "in": "path", - "name": "hook_id", - "type": "integer", - "required": true, - "description": "The numeric ID of the integration hook" + { + "name": "Account AgentBots", + "description": "Account-specific Agent Bots" }, - "source_id": { - "in": "path", - "name": "source_id", - "required": true, - "type": "string", - "description": "Id of the session for which the conversation is created.\n\n\n\n Source Ids can be obtained through contactable inboxes API or via generated.Website: Chatwoot generated string which can be obtained from webhook events. Phone Channels(Twilio): Phone number in e164 format Email Channels: Contact Email address API Channel: Any Random String" + { + "name": "Agents", + "description": "Agent management APIs" }, - "contact_sort_param": { - "in": "query", - "name": "sort", - "type": "string", - "enum": [ - "name", - "email", - "phone_number", - "last_activity_at", - "-name", - "-email", - "-phone_number", - "-last_activity_at" - ], - "required": false, - "description": "The attribute by which list should be sorted" + { + "name": "Canned Responses", + "description": "Pre-defined responses for common queries" }, - "conversation_id": { - "in": "path", - "name": "conversation_id", - "type": "integer", - "required": true, - "description": "The numeric ID of the conversation" + { + "name": "Contacts", + "description": "Contact management APIs" }, - "conversation_uuid": { - "in": "path", - "name": "conversation_uuid", - "type": "integer", - "required": true, - "description": "The uuid of the conversation" + { + "name": "Contact Labels", + "description": "Manage contact labels" }, - "custom_filter_id": { - "in": "path", - "name": "custom_filter_id", - "type": "integer", - "required": true, - "description": "The numeric ID of the custom filter" + { + "name": "Conversation Assignments", + "description": "Manage conversation assignments" }, - "webhook_id": { - "in": "path", - "name": "webhook_id", - "type": "integer", - "required": true, - "description": "The numeric ID of the webhook" + { + "name": "Conversation Labels", + "description": "Manage conversation labels" }, - "message_id": { - "in": "path", - "name": "message_id", - "type": "integer", - "required": true, - "description": "The numeric ID of the message" + { + "name": "Conversations", + "description": "Conversation management APIs" }, - "page": { - "in": "query", - "name": "page", - "type": "integer", - "default": 1, - "required": false, - "description": "The page parameter" + { + "name": "Custom Attributes", + "description": "Custom fields for contacts and conversations" }, - "platform_user_id": { - "in": "path", - "name": "id", - "type": "integer", - "required": true, - "description": "The numeric ID of the user on the platform" + { + "name": "Custom Filters", + "description": "Saved filters for conversations" }, - "report_type": { - "in": "query", - "name": "type", - "type": "string", - "enum": [ - "account", - "agent", - "inbox", - "label", - "team" - ], - "required": true, - "description": "Type of report" + { + "name": "Inboxes", + "description": "Communication channels setup" }, - "report_metric": { - "in": "query", - "name": "metric", - "type": "string", - "enum": [ - "conversations_count", - "incoming_messages_count", - "outgoing_messages_count", - "avg_first_response_time", - "avg_resolution_time", - "resolutions_count" - ], - "required": true, - "description": "The type of metric" + { + "name": "Integrations", + "description": "Third-party integrations" }, - "public_inbox_identifier": { - "in": "path", - "name": "inbox_identifier", - "type": "string", - "required": true, - "description": "The identifier obtained from API inbox channel" + { + "name": "Messages", + "description": "Message management APIs" }, - "public_contact_identifier": { - "in": "path", - "name": "contact_identifier", - "type": "string", - "required": true, - "description": "The source id of contact obtained on contact create" + { + "name": "Profile", + "description": "User profile APIs" }, - "portal_id": { - "in": "path", - "name": "portal_id", - "type": "integer", - "required": true, - "description": "The numeric ID of the portal" + { + "name": "Reports", + "description": "Analytics and reporting APIs" + }, + { + "name": "Teams", + "description": "Team management APIs" + }, + { + "name": "Webhooks", + "description": "Event notification webhooks" + }, + { + "name": "Automation Rule", + "description": "Workflow automation rules" + }, + { + "name": "Help Center", + "description": "Knowledge base management" + }, + { + "name": "Contacts API", + "description": "Public contact APIs" + }, + { + "name": "Conversations API", + "description": "Public conversation APIs" + }, + { + "name": "Messages API", + "description": "Public message APIs" + }, + { + "name": "CSAT Survey Page", + "description": "Customer satisfaction survey" } - }, + ], "x-tagGroups": [ { "name": "Platform", @@ -7574,7 +11821,7 @@ "Canned Responses", "Contacts", "Contact Labels", - "Conversation Assignment", + "Conversation Assignments", "Conversation Labels", "Conversations", "Custom Attributes", diff --git a/swagger/tag_groups/application.yml b/swagger/tag_groups/application.yml new file mode 100644 index 000000000..85d96c5b7 --- /dev/null +++ b/swagger/tag_groups/application.yml @@ -0,0 +1,65 @@ +openapi: '3.0.4' +info: + title: Chatwoot - Application API + description: Application API endpoints for Chatwoot + version: 1.1.0 + termsOfService: https://www.chatwoot.com/terms-of-service/ + contact: + email: hello@chatwoot.com + license: + name: MIT License + url: https://opensource.org/licenses/MIT +servers: + - url: https://app.chatwoot.com/ +tags: + - name: Account AgentBots + description: Manage agent bots within accounts + - name: Agents + description: Manage agents + - name: Canned Responses + description: Manage canned responses + - name: Contacts + description: Manage contacts + - name: Contact Labels + description: Manage contact labels + - name: Conversation Assignments + description: Manage conversation assignments + - name: Conversation Labels + description: Manage conversation labels + - name: Conversations + description: Manage conversations + - name: Custom Attributes + description: Manage custom attributes + - name: Custom Filters + description: Manage custom filters + - name: Inboxes + description: Manage inboxes + - name: Integrations + description: Manage integrations + - name: Messages + description: Manage messages + - name: Profile + description: Manage user profile + - name: Reports + description: Generate reports + - name: Teams + description: Manage teams + - name: Webhooks + description: Manage webhooks + - name: Automation Rule + description: Manage automation rules + - name: Help Center + description: Manage help center +paths: + $ref: ../paths/index.yml +components: + schemas: + $ref: ../definitions/index.yml + parameters: + $ref: ../parameters/index.yml + securitySchemes: + userApiKey: + type: apiKey + in: header + name: api_access_token + description: This token can be obtained by visiting the profile page or via rails console. Provides access to endpoints based on the user permissions levels. \ No newline at end of file diff --git a/swagger/tag_groups/application_swagger.json b/swagger/tag_groups/application_swagger.json new file mode 100644 index 000000000..33e75183f --- /dev/null +++ b/swagger/tag_groups/application_swagger.json @@ -0,0 +1,10184 @@ +{ + "openapi": "3.0.4", + "info": { + "title": "Chatwoot", + "description": "This is the API documentation for Chatwoot server.", + "version": "1.1.0", + "termsOfService": "https://www.chatwoot.com/terms-of-service/", + "contact": { + "email": "hello@chatwoot.com" + }, + "license": { + "name": "MIT License", + "url": "https://opensource.org/licenses/MIT" + } + }, + "servers": [ + { + "url": "https://app.chatwoot.com/" + } + ], + "paths": { + "/api/v1/accounts/{account_id}/agent_bots": { + "parameters": [ + { + "$ref": "#/components/parameters/account_id" + } + ], + "get": { + "tags": [ + "Account AgentBots" + ], + "operationId": "list-all-account-agent-bots", + "summary": "List all AgentBots", + "description": "List all agent bots available for the current account", + "security": [ + { + "userApiKey": [] + } + ], + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "type": "array", + "description": "Array of agent bots", + "items": { + "$ref": "#/components/schemas/agent_bot" + } + } + } + } + }, + "401": { + "description": "Unauthorized", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/bad_request_error" + } + } + } + } + } + }, + "post": { + "tags": [ + "Account AgentBots" + ], + "operationId": "create-an-account-agent-bot", + "summary": "Create an Agent Bot", + "description": "Create an agent bot in the account", + "security": [ + { + "userApiKey": [] + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/agent_bot_create_update_payload" + } + } + } + }, + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/agent_bot" + } + } + } + }, + "401": { + "description": "Unauthorized", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/bad_request_error" + } + } + } + } + } + } + }, + "/api/v1/accounts/{account_id}/agent_bots/{id}": { + "parameters": [ + { + "$ref": "#/components/parameters/account_id" + }, + { + "$ref": "#/components/parameters/agent_bot_id" + } + ], + "get": { + "tags": [ + "Account AgentBots" + ], + "operationId": "get-details-of-a-single-account-agent-bot", + "summary": "Get an agent bot details", + "description": "Get the details of an agent bot in the account", + "security": [ + { + "userApiKey": [] + } + ], + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/agent_bot" + } + } + } + }, + "401": { + "description": "Unauthorized", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/bad_request_error" + } + } + } + }, + "404": { + "description": "The given agent bot ID does not exist in the account", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/bad_request_error" + } + } + } + } + } + }, + "patch": { + "tags": [ + "Account AgentBots" + ], + "operationId": "update-an-account-agent-bot", + "summary": "Update an agent bot", + "description": "Update an agent bot's attributes", + "security": [ + { + "userApiKey": [] + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/agent_bot_create_update_payload" + } + } + } + }, + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/agent_bot" + } + } + } + }, + "401": { + "description": "Unauthorized", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/bad_request_error" + } + } + } + } + } + }, + "delete": { + "tags": [ + "Account AgentBots" + ], + "operationId": "delete-an-account-agent-bot", + "summary": "Delete an AgentBot", + "description": "Delete an AgentBot from the account", + "security": [ + { + "userApiKey": [] + } + ], + "responses": { + "200": { + "description": "Success" + }, + "401": { + "description": "Unauthorized", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/bad_request_error" + } + } + } + }, + "404": { + "description": "The agent bot does not exist in the account", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/bad_request_error" + } + } + } + } + } + } + }, + "/api/v1/accounts/{account_id}/agents": { + "parameters": [ + { + "$ref": "#/components/parameters/account_id" + } + ], + "get": { + "tags": [ + "Agents" + ], + "operationId": "get-account-agents", + "summary": "List Agents in Account", + "description": "Get Details of Agents in an Account", + "security": [ + { + "userApiKey": [] + } + ], + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "type": "array", + "description": "Array of all active agents", + "items": { + "$ref": "#/components/schemas/agent" + } + } + } + } + }, + "403": { + "description": "Access denied", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/bad_request_error" + } + } + } + } + } + }, + "post": { + "tags": [ + "Agents" + ], + "operationId": "add-new-agent-to-account", + "summary": "Add a New Agent", + "description": "Add a new Agent to Account", + "security": [ + { + "userApiKey": [] + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/agent_create_payload" + } + } + } + }, + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/agent" + } + } + } + }, + "403": { + "description": "Access denied", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/bad_request_error" + } + } + } + } + } + } + }, + "/api/v1/accounts/{account_id}/agents/{id}": { + "parameters": [ + { + "$ref": "#/components/parameters/account_id" + } + ], + "patch": { + "tags": [ + "Agents" + ], + "operationId": "update-agent-in-account", + "summary": "Update Agent in Account", + "description": "Update an Agent in Account", + "security": [ + { + "userApiKey": [] + } + ], + "parameters": [ + { + "in": "path", + "name": "id", + "schema": { + "type": "integer" + }, + "required": true, + "description": "The ID of the agent to be updated." + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/agent_update_payload" + } + } + } + }, + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/agent" + } + } + } + }, + "404": { + "description": "Agent 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" + } + } + } + } + } + }, + "delete": { + "tags": [ + "Agents" + ], + "operationId": "delete-agent-from-account", + "summary": "Remove an Agent from Account", + "description": "Remove an Agent from Account", + "security": [ + { + "userApiKey": [] + } + ], + "parameters": [ + { + "in": "path", + "name": "id", + "schema": { + "type": "integer" + }, + "required": true, + "description": "The ID of the agent to be deleted." + } + ], + "responses": { + "200": { + "description": "Success" + }, + "404": { + "description": "Agent 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" + } + } + } + } + } + } + }, + "/api/v1/accounts/{account_id}/canned_responses": { + "parameters": [ + { + "$ref": "#/components/parameters/account_id" + } + ], + "get": { + "tags": [ + "Canned Responses" + ], + "operationId": "get-account-canned-response", + "summary": "List all Canned Responses in an Account", + "description": "Get Details of Canned Responses in an Account", + "security": [ + { + "userApiKey": [] + } + ], + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "type": "array", + "description": "Array of all canned responses", + "items": { + "$ref": "#/components/schemas/canned_response" + } + } + } + } + }, + "403": { + "description": "Access denied", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/bad_request_error" + } + } + } + } + } + }, + "post": { + "tags": [ + "Canned Responses" + ], + "operationId": "add-new-canned-response-to-account", + "summary": "Add a New Canned Response", + "description": "Add a new Canned Response to Account", + "security": [ + { + "userApiKey": [] + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/canned_response_create_update_payload" + } + } + } + }, + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/canned_response" + } + } + } + }, + "403": { + "description": "Access denied", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/bad_request_error" + } + } + } + } + } + } + }, + "/api/v1/accounts/{account_id}/canned_responses/{id}": { + "parameters": [ + { + "$ref": "#/components/parameters/account_id" + } + ], + "patch": { + "tags": [ + "Canned Responses" + ], + "operationId": "update-canned-response-in-account", + "summary": "Update Canned Response in Account", + "description": "Update a Canned Response in Account", + "security": [ + { + "userApiKey": [] + } + ], + "parameters": [ + { + "in": "path", + "name": "id", + "schema": { + "type": "integer" + }, + "required": true, + "description": "The ID of the canned response to be updated." + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/canned_response_create_update_payload" + } + } + } + }, + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/canned_response" + } + } + } + }, + "404": { + "description": "Agent 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" + } + } + } + } + } + }, + "delete": { + "tags": [ + "Canned Responses" + ], + "operationId": "delete-canned-response-from-account", + "summary": "Remove a Canned Response from Account", + "description": "Remove a Canned Response from Account", + "security": [ + { + "userApiKey": [] + } + ], + "parameters": [ + { + "in": "path", + "name": "id", + "schema": { + "type": "integer" + }, + "required": true, + "description": "The ID of the canned response to be deleted" + } + ], + "responses": { + "200": { + "description": "Success" + }, + "404": { + "description": "Canned Response 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" + } + } + } + } + } + } + }, + "/api/v1/accounts/{account_id}/custom_attribute_definitions": { + "parameters": [ + { + "$ref": "#/components/parameters/account_id" + } + ], + "get": { + "tags": [ + "Custom Attributes" + ], + "operationId": "get-account-custom-attribute", + "summary": "List all custom attributes in an account", + "parameters": [ + { + "name": "attribute_model", + "in": "query", + "schema": { + "type": "string", + "enum": [ + "0", + "1" + ] + }, + "description": "conversation_attribute(0)/contact_attribute(1)", + "required": true + } + ], + "description": "Get details of custom attributes in an Account", + "security": [ + { + "userApiKey": [] + } + ], + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "type": "array", + "description": "Array of all custom attributes", + "items": { + "$ref": "#/components/schemas/custom_attribute" + } + } + } + } + }, + "403": { + "description": "Access denied", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/bad_request_error" + } + } + } + } + } + }, + "post": { + "tags": [ + "Custom Attributes" + ], + "operationId": "add-new-custom-attribute-to-account", + "summary": "Add a new custom attribute", + "description": "Add a new custom attribute to account", + "security": [ + { + "userApiKey": [] + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/custom_attribute_create_update_payload" + } + } + } + }, + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/custom_attribute" + } + } + } + }, + "403": { + "description": "Access denied", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/bad_request_error" + } + } + } + } + } + } + }, + "/api/v1/accounts/{account_id}/custom_attribute_definitions/{id}": { + "parameters": [ + { + "$ref": "#/components/parameters/account_id" + }, + { + "name": "id", + "in": "path", + "schema": { + "type": "number" + }, + "description": "ID of the custom attribute", + "required": true + } + ], + "get": { + "tags": [ + "Custom Attributes" + ], + "operationId": "get-details-of-a-single-custom-attribute", + "summary": "Get a custom attribute details", + "security": [ + { + "userApiKey": [] + } + ], + "description": "Get the details of a custom attribute in the account", + "parameters": [ + { + "$ref": "#/components/parameters/account_id" + }, + { + "in": "path", + "name": "id", + "schema": { + "type": "integer" + }, + "required": true, + "description": "The ID of the custom attribute to be updated." + } + ], + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/custom_attribute" + } + } + } + }, + "401": { + "description": "Unauthorized", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/bad_request_error" + } + } + } + }, + "404": { + "description": "The given attribute ID does not exist in the account", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/bad_request_error" + } + } + } + } + } + }, + "patch": { + "tags": [ + "Custom Attributes" + ], + "operationId": "update-custom-attribute-in-account", + "summary": "Update custom attribute in Account", + "description": "Update a custom attribute in account", + "security": [ + { + "userApiKey": [] + } + ], + "parameters": [ + { + "in": "path", + "name": "id", + "schema": { + "type": "integer" + }, + "required": true, + "description": "The ID of the custom attribute to be updated." + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/custom_attribute_create_update_payload" + } + } + } + }, + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/custom_attribute" + } + } + } + }, + "404": { + "description": "Agent 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" + } + } + } + } + } + }, + "delete": { + "tags": [ + "Custom Attributes" + ], + "operationId": "delete-custom-attribute-from-account", + "summary": "Remove a custom attribute from account", + "description": "Remove a custom attribute from account", + "security": [ + { + "userApiKey": [] + } + ], + "parameters": [ + { + "$ref": "#/components/parameters/account_id" + }, + { + "in": "path", + "name": "id", + "schema": { + "type": "integer" + }, + "required": true, + "description": "The ID of the custom attribute to be deleted" + } + ], + "responses": { + "200": { + "description": "Success" + }, + "404": { + "description": "Custom attribute 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" + } + } + } + } + } + } + }, + "/api/v1/accounts/{account_id}/contacts": { + "get": { + "tags": [ + "Contacts" + ], + "operationId": "contactList", + "description": "Listing all the resolved contacts with pagination (Page size = 15). Resolved contacts are the ones with a value for identifier, email or phone number", + "summary": "List Contacts", + "security": [ + { + "userApiKey": [] + } + ], + "parameters": [ + { + "$ref": "#/components/parameters/account_id" + }, + { + "$ref": "#/components/parameters/contact_sort_param" + }, + { + "$ref": "#/components/parameters/page" + } + ], + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/contacts_list_response" + } + } + } + }, + "400": { + "description": "Bad Request Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/bad_request_error" + } + } + } + } + } + }, + "post": { + "tags": [ + "Contacts" + ], + "operationId": "contactCreate", + "description": "Create a new Contact", + "summary": "Create Contact", + "security": [ + { + "userApiKey": [] + } + ], + "parameters": [ + { + "$ref": "#/components/parameters/account_id" + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/contact_create_payload" + } + } + } + }, + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/extended_contact" + } + } + } + }, + "400": { + "description": "Bad Request Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/bad_request_error" + } + } + } + } + } + } + }, + "/api/v1/accounts/{account_id}/contacts/{id}": { + "parameters": [ + { + "$ref": "#/components/parameters/account_id" + }, + { + "name": "id", + "in": "path", + "required": true, + "schema": { + "type": "number" + }, + "description": "ID of the contact" + } + ], + "get": { + "tags": [ + "Contacts" + ], + "operationId": "contactDetails", + "summary": "Show Contact", + "security": [ + { + "userApiKey": [] + } + ], + "description": "Get a contact belonging to the account using ID", + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/contact_show_response" + } + } + } + }, + "404": { + "description": "Contact 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" + } + } + } + } + } + }, + "put": { + "tags": [ + "Contacts" + ], + "operationId": "contactUpdate", + "summary": "Update Contact", + "security": [ + { + "userApiKey": [] + } + ], + "description": "Update a contact belonging to the account using ID", + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/contact_update_payload" + } + } + } + }, + "responses": { + "204": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/contact_base" + } + } + } + }, + "404": { + "description": "Contact 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" + } + } + } + } + } + }, + "delete": { + "tags": [ + "Contacts" + ], + "operationId": "contactDelete", + "summary": "Delete Contact", + "security": [ + { + "userApiKey": [] + } + ], + "description": "Delete a contact belonging to the account using ID", + "responses": { + "200": { + "description": "Success" + }, + "401": { + "description": "Unauthorized", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/bad_request_error" + } + } + } + }, + "404": { + "description": "Contact not found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/bad_request_error" + } + } + } + } + } + } + }, + "/api/v1/accounts/{account_id}/contacts/{id}/conversations": { + "parameters": [ + { + "$ref": "#/components/parameters/account_id" + }, + { + "name": "id", + "in": "path", + "required": true, + "schema": { + "type": "number" + }, + "description": "ID of the contact" + } + ], + "get": { + "tags": [ + "Contacts" + ], + "operationId": "contactConversations", + "summary": "Contact Conversations", + "description": "Get conversations associated with that contact", + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "schema": { + "type": "number" + }, + "description": "ID of the contact" + } + ], + "security": [ + { + "userApiKey": [] + } + ], + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/contact_conversations_response" + } + } + } + }, + "404": { + "description": "Contact 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" + } + } + } + } + } + } + }, + "/api/v1/accounts/{account_id}/contacts/{id}/labels": { + "parameters": [ + { + "$ref": "#/components/parameters/account_id" + }, + { + "name": "id", + "in": "path", + "required": true, + "schema": { + "type": "number" + }, + "description": "ID of the contact" + } + ], + "get": { + "tags": [ + "Contact Labels" + ], + "operationId": "list-all-labels-of-a-contact", + "summary": "List Labels", + "description": "Lists all the labels of a contact", + "security": [ + { + "userApiKey": [] + } + ], + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/contact_labels" + } + } + } + }, + "401": { + "description": "Unauthorized", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/bad_request_error" + } + } + } + }, + "404": { + "description": "Contact not found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/bad_request_error" + } + } + } + } + } + }, + "post": { + "tags": [ + "Contact Labels" + ], + "operationId": "contact-add-labels", + "summary": "Add Labels", + "description": "Add labels to a contact. Note that this API would overwrite the existing list of labels associated to the conversation.", + "security": [ + { + "userApiKey": [] + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "labels" + ], + "properties": { + "labels": { + "type": "array", + "description": "Array of labels (comma-separated strings)", + "items": { + "type": "string" + }, + "example": [ + "support", + "billing" + ] + } + } + } + } + } + }, + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/contact_labels" + } + } + } + }, + "401": { + "description": "Unauthorized", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/bad_request_error" + } + } + } + }, + "404": { + "description": "Contact not found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/bad_request_error" + } + } + } + } + } + } + }, + "/api/v1/accounts/{account_id}/contacts/search": { + "get": { + "tags": [ + "Contacts" + ], + "operationId": "contactSearch", + "description": "Search the resolved contacts using a search key, currently supports email search (Page size = 15). Resolved contacts are the ones with a value for identifier, email or phone number", + "summary": "Search Contacts", + "security": [ + { + "userApiKey": [] + } + ], + "parameters": [ + { + "$ref": "#/components/parameters/account_id" + }, + { + "name": "q", + "in": "query", + "schema": { + "type": "string" + }, + "description": "Search using contact `name`, `identifier`, `email` or `phone number`" + }, + { + "$ref": "#/components/parameters/contact_sort_param" + }, + { + "$ref": "#/components/parameters/page" + } + ], + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/contacts_list_response" + } + } + } + }, + "401": { + "description": "Authentication error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/bad_request_error" + } + } + } + } + } + } + }, + "/api/v1/accounts/{account_id}/contacts/filter": { + "post": { + "tags": [ + "Contacts" + ], + "operationId": "contactFilter", + "description": "Filter contacts with custom filter options and pagination", + "summary": "Contact Filter", + "security": [ + { + "userApiKey": [] + } + ], + "parameters": [ + { + "$ref": "#/components/parameters/account_id" + }, + { + "name": "page", + "in": "query", + "schema": { + "type": "number" + } + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "payload": { + "type": "array", + "items": { + "type": "object", + "properties": { + "attribute_key": { + "type": "string", + "description": "filter attribute name" + }, + "filter_operator": { + "type": "string", + "description": "filter operator name", + "enum": [ + "equal_to", + "not_equal_to", + "contains", + "does_not_contain" + ] + }, + "values": { + "type": "array", + "items": { + "type": "string" + }, + "description": "array of the attribute values to filter" + }, + "query_operator": { + "type": "string", + "description": "query operator name", + "enum": [ + "AND", + "OR" + ] + } + } + }, + "example": [ + { + "attribute_key": "name", + "filter_operator": "equal_to", + "values": [ + "en" + ], + "query_operator": "AND" + }, + { + "attribute_key": "country_code", + "filter_operator": "equal_to", + "values": [ + "us" + ], + "query_operator": null + } + ] + } + } + } + } + } + }, + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/contacts_list_response" + } + } + } + }, + "400": { + "description": "Bad Request Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/bad_request_error" + } + } + } + } + } + } + }, + "/api/v1/accounts/{account_id}/automation_rules": { + "parameters": [ + { + "$ref": "#/components/parameters/account_id" + } + ], + "get": { + "tags": [ + "Automation Rule" + ], + "operationId": "get-account-automation-rule", + "summary": "List all automation rules in an account", + "parameters": [ + { + "$ref": "#/components/parameters/account_id" + }, + { + "$ref": "#/components/parameters/page" + } + ], + "description": "Get details of automation rules in an Account", + "security": [ + { + "userApiKey": [] + } + ], + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/automation_rule" + } + } + } + }, + "403": { + "description": "Access denied", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/bad_request_error" + } + } + } + } + } + }, + "post": { + "tags": [ + "Automation Rule" + ], + "operationId": "add-new-automation-rule-to-account", + "summary": "Add a new automation rule", + "description": "Add a new automation rule to account", + "security": [ + { + "userApiKey": [] + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/automation_rule_create_update_payload" + } + } + } + }, + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/automation_rule" + } + } + } + }, + "403": { + "description": "Access denied", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/bad_request_error" + } + } + } + } + } + } + }, + "/api/v1/accounts/{account_id}/automation_rules/{id}": { + "parameters": [ + { + "$ref": "#/components/parameters/account_id" + }, + { + "name": "id", + "in": "path", + "schema": { + "type": "number" + }, + "description": "ID of the Automation Rule", + "required": true + } + ], + "get": { + "tags": [ + "Automation Rule" + ], + "operationId": "get-details-of-a-single-automation-rule", + "summary": "Get a automation rule details", + "description": "Get the details of a automation rule in the account", + "security": [ + { + "userApiKey": [] + } + ], + "parameters": [ + { + "in": "path", + "name": "id", + "schema": { + "type": "integer" + }, + "required": true, + "description": "The ID of the automation rule to be updated." + } + ], + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/automation_rule" + }, + "example": { + "payload": { + "id": 90, + "account_id": 1, + "name": "add-label-bug-if-message-contains-bug", + "description": "add-label-bug-if-message-contains-bug", + "event_name": "message_created", + "conditions": [ + { + "values": [ + "incoming" + ], + "attribute_key": "message_type", + "query_operator": "and", + "filter_operator": "equal_to" + }, + { + "values": [ + "bug" + ], + "attribute_key": "content", + "filter_operator": "contains" + } + ], + "actions": [ + { + "action_name": "add_label", + "action_params": [ + "bugs", + "support-query" + ] + } + ], + "created_on": 1650555440, + "active": true + } + } + } + } + }, + "401": { + "description": "Unauthorized", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/bad_request_error" + } + } + } + }, + "404": { + "description": "The given rule ID does not exist in the account", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/bad_request_error" + } + } + } + } + } + }, + "patch": { + "tags": [ + "Automation Rule" + ], + "operationId": "update-automation-rule-in-account", + "summary": "Update automation rule in Account", + "description": "Update a automation rule in account", + "security": [ + { + "userApiKey": [] + } + ], + "parameters": [ + { + "in": "path", + "name": "id", + "schema": { + "type": "integer" + }, + "required": true, + "description": "The ID of the automation rule to be updated." + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/automation_rule_create_update_payload" + } + } + } + }, + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/automation_rule" + } + } + } + }, + "403": { + "description": "Access denied", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/bad_request_error" + } + } + } + }, + "404": { + "description": "Rule not found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/bad_request_error" + } + } + } + } + } + }, + "delete": { + "tags": [ + "Automation Rule" + ], + "operationId": "delete-automation-rule-from-account", + "summary": "Remove a automation rule from account", + "description": "Remove a automation rule from account", + "security": [ + { + "userApiKey": [] + } + ], + "parameters": [ + { + "in": "path", + "name": "id", + "schema": { + "type": "integer" + }, + "required": true, + "description": "The ID of the automation rule to be deleted" + } + ], + "responses": { + "200": { + "description": "Success" + }, + "403": { + "description": "Access denied", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/bad_request_error" + } + } + } + }, + "404": { + "description": "automation rule not found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/bad_request_error" + } + } + } + } + } + } + }, + "/api/v1/accounts/{account_id}/portals": { + "parameters": [ + { + "$ref": "#/components/parameters/account_id" + } + ], + "post": { + "tags": [ + "Help Center" + ], + "operationId": "add-new-portal-to-account", + "summary": "Add a new portal", + "description": "Add a new portal to account", + "security": [ + { + "userApiKey": [] + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/portal_create_update_payload" + } + } + } + }, + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/portal" + } + } + } + }, + "403": { + "description": "Access denied", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/bad_request_error" + } + } + } + } + } + }, + "get": { + "tags": [ + "Help Center" + ], + "operationId": "get-portal", + "summary": "List all portals in an account", + "parameters": [ + { + "$ref": "#/components/parameters/account_id" + } + ], + "description": "Get details of portals in an Account", + "security": [ + { + "userApiKey": [] + } + ], + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/portal" + }, + "example": { + "payload": [ + { + "id": 4, + "color": "#1F93FF", + "custom_domain": "chatwoot.help", + "header_text": "Handbook", + "homepage_link": "https://www.chatwoot.com", + "name": "Handbook", + "page_title": "Handbook", + "slug": "handbook", + "archived": false, + "account_id": 1, + "config": { + "allowed_locales": [ + { + "code": "en", + "articles_count": 32, + "categories_count": 9 + } + ] + }, + "inbox": { + "id": 37, + "avatar_url": "https://example.com/avatar.png", + "channel_id": 1, + "name": "Chatwoot", + "channel_type": "Channel::WebWidget", + "greeting_enabled": true, + "widget_color": "#1F93FF", + "website_url": "chatwoot.com" + }, + "logo": { + "id": 19399916, + "portal_id": 4, + "file_type": "image/png", + "account_id": 1, + "file_url": "https://example.com/logo.png", + "blob_id": 21239614, + "filename": "square.png" + }, + "meta": { + "all_articles_count": 0, + "categories_count": 9, + "default_locale": "en" + } + } + ] + } + } + } + }, + "403": { + "description": "Access denied", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/bad_request_error" + } + } + } + } + } + } + }, + "/api/v1/accounts/{account_id}/portals/{id}": { + "parameters": [ + { + "$ref": "#/components/parameters/account_id" + }, + { + "$ref": "#/components/parameters/portal_id" + } + ], + "patch": { + "tags": [ + "Help Center" + ], + "operationId": "update-portal-to-account", + "summary": "Update a portal", + "description": "Update a portal to account", + "security": [ + { + "userApiKey": [] + } + ], + "parameters": [ + { + "$ref": "#/components/parameters/account_id" + }, + { + "$ref": "#/components/parameters/portal_id" + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/portal_create_update_payload" + } + } + } + }, + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/portal_single" + }, + "example": { + "payload": { + "id": 123, + "archived": false, + "color": "#1F93FF", + "config": { + "allowed_locales": [ + { + "code": "en", + "articles_count": 32, + "categories_count": 9 + } + ] + }, + "custom_domain": "chatwoot.help", + "header_text": "Handbook", + "homepage_link": "https://www.chatwoot.com", + "name": "Handbook", + "slug": "handbook", + "page_title": "Handbook", + "account_id": 123, + "inbox": { + "id": 123, + "name": "Chatwoot", + "website_url": "chatwoot.com", + "channel_type": "Channel::WebWidget", + "avatar_url": "https://example.com/avatar.png", + "widget_color": "#1F93FF", + "website_token": "4cWzuf9i9jxN9tbnv8K9STKU", + "enable_auto_assignment": true, + "web_widget_script": "", + "welcome_title": "Hi there ! 🙌🏼", + "welcome_tagline": "We make it simple to connect with us.", + "greeting_enabled": true, + "greeting_message": "Hey there 👋, Thank you for reaching out to us.", + "channel_id": 123, + "working_hours_enabled": true, + "enable_email_collect": true, + "csat_survey_enabled": true, + "timezone": "America/Los_Angeles", + "business_name": "Chatwoot", + "hmac_mandatory": true + }, + "logo": { + "id": 123, + "portal_id": 123, + "file_type": "image/png", + "account_id": 123, + "file_url": "https://example.com/logo.png", + "blob_id": 123, + "filename": "square.png" + }, + "meta": { + "all_articles_count": 32, + "categories_count": 9, + "default_locale": "en" + } + } + } + } + } + }, + "403": { + "description": "Access denied", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/bad_request_error" + } + } + } + }, + "404": { + "description": "Portal not found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/bad_request_error" + } + } + } + } + } + } + }, + "/api/v1/accounts/{account_id}/portals/{id}/categories": { + "parameters": [ + { + "$ref": "#/components/parameters/account_id" + }, + { + "$ref": "#/components/parameters/portal_id" + } + ], + "post": { + "tags": [ + "Help Center" + ], + "operationId": "add-new-category-to-account", + "summary": "Add a new category", + "description": "Add a new category to portal", + "security": [ + { + "userApiKey": [] + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/category_create_update_payload" + } + } + } + }, + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/category" + } + } + } + }, + "403": { + "description": "Access denied", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/bad_request_error" + } + } + } + } + } + } + }, + "/api/v1/accounts/{account_id}/portals/{id}/articles": { + "parameters": [ + { + "$ref": "#/components/parameters/account_id" + }, + { + "$ref": "#/components/parameters/portal_id" + } + ], + "post": { + "tags": [ + "Help Center" + ], + "operationId": "add-new-article-to-account", + "summary": "Add a new article", + "description": "Add a new article to portal", + "security": [ + { + "userApiKey": [] + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/article_create_update_payload" + } + } + } + }, + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/article" + } + } + } + }, + "403": { + "description": "Access denied", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/bad_request_error" + } + } + } + } + } + } + }, + "/api/v1/accounts/{account_id}/conversations/meta": { + "parameters": [ + { + "$ref": "#/components/parameters/account_id" + } + ], + "get": { + "tags": [ + "Conversations" + ], + "operationId": "conversationListMeta", + "description": "Get open, unassigned and all Conversation counts", + "summary": "Get Conversation Counts", + "security": [ + { + "userApiKey": [] + } + ], + "parameters": [ + { + "name": "status", + "in": "query", + "schema": { + "type": "string", + "enum": [ + "all", + "open", + "resolved", + "pending", + "snoozed" + ], + "default": "open" + }, + "description": "Filter by conversation status." + }, + { + "name": "q", + "in": "query", + "schema": { + "type": "string" + }, + "description": "Filters conversations with messages containing the search term" + }, + { + "name": "inbox_id", + "in": "query", + "schema": { + "type": "integer" + } + }, + { + "name": "team_id", + "in": "query", + "schema": { + "type": "integer" + } + }, + { + "name": "labels", + "in": "query", + "schema": { + "type": "array", + "items": { + "type": "string" + } + } + } + ], + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "meta": { + "type": "object", + "properties": { + "mine_count": { + "type": "number" + }, + "unassigned_count": { + "type": "number" + }, + "assigned_count": { + "type": "number" + }, + "all_count": { + "type": "number" + } + } + } + } + } + } + } + }, + "400": { + "description": "Bad Request Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/bad_request_error" + } + } + } + } + } + } + }, + "/api/v1/accounts/{account_id}/conversations": { + "parameters": [ + { + "$ref": "#/components/parameters/account_id" + } + ], + "get": { + "tags": [ + "Conversations" + ], + "operationId": "conversationList", + "description": "List all the conversations with pagination", + "summary": "Conversations List", + "security": [ + { + "userApiKey": [] + } + ], + "parameters": [ + { + "name": "assignee_type", + "in": "query", + "schema": { + "type": "string", + "enum": [ + "me", + "unassigned", + "all", + "assigned" + ], + "default": "all" + }, + "description": "Filter conversations by assignee type." + }, + { + "name": "status", + "in": "query", + "schema": { + "type": "string", + "enum": [ + "all", + "open", + "resolved", + "pending", + "snoozed" + ], + "default": "open" + }, + "description": "Filter by conversation status." + }, + { + "name": "q", + "in": "query", + "schema": { + "type": "string" + }, + "description": "Filters conversations with messages containing the search term" + }, + { + "name": "inbox_id", + "in": "query", + "schema": { + "type": "integer" + } + }, + { + "name": "team_id", + "in": "query", + "schema": { + "type": "integer" + } + }, + { + "name": "labels", + "in": "query", + "schema": { + "type": "array", + "items": { + "type": "string" + } + } + }, + { + "name": "page", + "in": "query", + "schema": { + "type": "integer", + "default": 1 + }, + "description": "paginate through conversations" + } + ], + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/conversation_list" + } + } + } + }, + "400": { + "description": "Bad Request Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/bad_request_error" + } + } + } + } + } + }, + "post": { + "tags": [ + "Conversations" + ], + "operationId": "newConversation", + "summary": "Create New Conversation", + "description": "Creating a conversation in chatwoot requires a source id. \n\n Learn more about source_id: https://www.chatwoot.com/hc/user-guide/articles/1677839703-how-to-create-an-api-channel-inbox#send-messages-to-the-api-channel", + "security": [ + { + "userApiKey": [] + }, + { + "agentBotApiKey": [] + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/conversation_create_payload" + } + } + } + }, + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "id": { + "type": "number", + "description": "ID of the conversation" + }, + "account_id": { + "type": "number", + "description": "Account Id" + }, + "inbox_id": { + "type": "number", + "description": "ID of the inbox" + } + } + } + } + } + }, + "403": { + "description": "Access denied", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/bad_request_error" + } + } + } + } + } + } + }, + "/api/v1/accounts/{account_id}/conversations/filter": { + "parameters": [ + { + "$ref": "#/components/parameters/account_id" + } + ], + "post": { + "tags": [ + "Conversations" + ], + "operationId": "conversationFilter", + "description": "Filter conversations with custom filter options and pagination", + "summary": "Conversations Filter", + "security": [ + { + "userApiKey": [] + } + ], + "parameters": [ + { + "name": "page", + "in": "query", + "schema": { + "type": "number" + } + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "payload": { + "type": "array", + "items": { + "type": "object", + "properties": { + "attribute_key": { + "type": "string", + "description": "filter attribute name" + }, + "filter_operator": { + "type": "string", + "description": "filter operator name", + "enum": [ + "equal_to", + "not_equal_to", + "contains", + "does_not_contain" + ] + }, + "values": { + "type": "array", + "items": { + "type": "string" + }, + "description": "array of the attribute values to filter" + }, + "query_operator": { + "type": "string", + "description": "query operator name", + "enum": [ + "AND", + "OR" + ] + } + } + }, + "example": [ + { + "attribute_key": "browser_language", + "filter_operator": "not_equal_to", + "values": [ + "en" + ], + "query_operator": "AND" + }, + { + "attribute_key": "status", + "filter_operator": "equal_to", + "values": [ + "pending" + ], + "query_operator": null + } + ] + } + } + } + } + } + }, + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/conversation_list" + } + } + } + }, + "400": { + "description": "Bad Request Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/bad_request_error" + } + } + } + } + } + } + }, + "/api/v1/accounts/{account_id}/conversations/{conversation_id}": { + "parameters": [ + { + "$ref": "#/components/parameters/account_id" + }, + { + "$ref": "#/components/parameters/conversation_id" + } + ], + "get": { + "tags": [ + "Conversations" + ], + "operationId": "get-details-of-a-conversation", + "summary": "Conversation Details", + "security": [ + { + "userApiKey": [] + } + ], + "description": "Get all details regarding a conversation with all messages in the conversation", + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/conversation_show" + } + } + } + }, + "403": { + "description": "Access denied", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/bad_request_error" + } + } + } + }, + "404": { + "description": "Conversation not found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/bad_request_error" + } + } + } + } + } + }, + "patch": { + "tags": [ + "Conversations" + ], + "operationId": "update-conversation", + "summary": "Update Conversation", + "description": "Update Conversation Attributes", + "security": [ + { + "userApiKey": [] + }, + { + "agentBotApiKey": [] + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "priority": { + "type": "string", + "enum": [ + "urgent", + "high", + "medium", + "low", + "none" + ], + "description": "The priority of the conversation", + "example": "high" + }, + "sla_policy_id": { + "type": "number", + "description": "The ID of the SLA policy (Available only in Enterprise edition)", + "example": 1 + } + } + } + } + } + }, + "responses": { + "200": { + "description": "Success" + }, + "401": { + "description": "Unauthorized", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/bad_request_error" + } + } + } + }, + "404": { + "description": "Conversation not found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/bad_request_error" + } + } + } + } + } + } + }, + "/api/v1/accounts/{account_id}/conversations/{conversation_id}/toggle_status": { + "parameters": [ + { + "$ref": "#/components/parameters/account_id" + }, + { + "$ref": "#/components/parameters/conversation_id" + } + ], + "post": { + "tags": [ + "Conversations" + ], + "operationId": "toggle-status-of-a-conversation", + "summary": "Toggle Status", + "description": "Toggles the status of the conversation between open and resolved", + "security": [ + { + "userApiKey": [] + }, + { + "agentBotApiKey": [] + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "status" + ], + "properties": { + "status": { + "type": "string", + "enum": [ + "open", + "resolved", + "pending" + ], + "description": "The status of the conversation", + "example": "open" + } + } + } + } + } + }, + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/conversation_status_toggle" + } + } + } + }, + "404": { + "description": "Conversation not found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/bad_request_error" + } + } + } + }, + "401": { + "description": "Unauthorized", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/bad_request_error" + } + } + } + } + } + } + }, + "/api/v1/accounts/{account_id}/conversations/{conversation_id}/toggle_priority": { + "parameters": [ + { + "$ref": "#/components/parameters/account_id" + }, + { + "$ref": "#/components/parameters/conversation_id" + } + ], + "post": { + "tags": [ + "Conversations" + ], + "operationId": "toggle-priority-of-a-conversation", + "summary": "Toggle Priority", + "description": "Toggles the priority of conversation", + "security": [ + { + "userApiKey": [] + }, + { + "agentBotApiKey": [] + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "priority" + ], + "properties": { + "priority": { + "type": "string", + "enum": [ + "urgent", + "high", + "medium", + "low", + "none" + ], + "description": "The priority of the conversation", + "example": "high" + } + } + } + } + } + }, + "responses": { + "200": { + "description": "Success" + }, + "401": { + "description": "Unauthorized", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/bad_request_error" + } + } + } + }, + "404": { + "description": "Conversation not found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/bad_request_error" + } + } + } + } + } + } + }, + "/api/v1/accounts/{account_id}/conversations/{conversation_id}/custom_attributes": { + "parameters": [ + { + "$ref": "#/components/parameters/account_id" + }, + { + "$ref": "#/components/parameters/conversation_id" + } + ], + "post": { + "tags": [ + "Conversations" + ], + "operationId": "update-custom-attributes-of-a-conversation", + "summary": "Update Custom Attributes", + "description": "Updates the custom attributes of a conversation", + "security": [ + { + "userApiKey": [] + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "custom_attributes" + ], + "properties": { + "custom_attributes": { + "type": "object", + "description": "The custom attributes to be set for the conversation", + "example": { + "order_id": "12345", + "previous_conversation": "67890" + } + } + } + } + } + } + }, + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "custom_attributes": { + "type": "object", + "description": "The custom attributes of the conversation" + } + } + } + } + } + }, + "401": { + "description": "Unauthorized", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/bad_request_error" + } + } + } + }, + "404": { + "description": "Conversation not found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/bad_request_error" + } + } + } + } + } + } + }, + "/api/v1/accounts/{account_id}/conversations/{conversation_id}/assignments": { + "parameters": [ + { + "$ref": "#/components/parameters/account_id" + }, + { + "$ref": "#/components/parameters/conversation_id" + } + ], + "post": { + "tags": [ + "Conversation Assignments" + ], + "operationId": "assign-a-conversation", + "summary": "Assign Conversation", + "description": "Assign a conversation to an agent or a team", + "security": [ + { + "userApiKey": [] + }, + { + "agentBotApiKey": [] + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "assignee_id": { + "type": "number", + "description": "Id of the assignee user", + "example": 1 + }, + "team_id": { + "type": "number", + "description": "Id of the team. If the assignee_id is present, this param would be ignored", + "example": 1 + } + } + } + } + } + }, + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/user" + } + } + } + }, + "401": { + "description": "Unauthorized", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/bad_request_error" + } + } + } + }, + "404": { + "description": "Conversation not found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/bad_request_error" + } + } + } + } + } + } + }, + "/api/v1/accounts/{account_id}/conversations/{conversation_id}/labels": { + "parameters": [ + { + "$ref": "#/components/parameters/account_id" + }, + { + "$ref": "#/components/parameters/conversation_id" + } + ], + "get": { + "tags": [ + "Conversations" + ], + "operationId": "list-all-labels-of-a-conversation", + "summary": "List Labels", + "security": [ + { + "userApiKey": [] + } + ], + "description": "Lists all the labels of a conversation", + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/conversation_labels" + } + } + } + }, + "404": { + "description": "Conversation not found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/bad_request_error" + } + } + } + }, + "401": { + "description": "Unauthorized", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/bad_request_error" + } + } + } + } + } + }, + "post": { + "tags": [ + "Conversations" + ], + "operationId": "conversation-add-labels", + "summary": "Add Labels", + "security": [ + { + "userApiKey": [] + } + ], + "description": "Add labels to a conversation. Note that this API would overwrite the existing list of labels associated to the conversation.", + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "labels" + ], + "properties": { + "labels": { + "type": "array", + "description": "Array of labels (comma-separated strings)", + "items": { + "type": "string" + }, + "example": [ + "support", + "billing" + ] + } + } + } + } + } + }, + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/conversation_labels" + } + } + } + }, + "404": { + "description": "Conversation not found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/bad_request_error" + } + } + } + }, + "401": { + "description": "Unauthorized", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/bad_request_error" + } + } + } + } + } + } + }, + "/api/v1/accounts/{account_id}/inboxes": { + "get": { + "tags": [ + "Inboxes" + ], + "operationId": "listAllInboxes", + "summary": "List all inboxes", + "description": "List all inboxes available in the current account", + "security": [ + { + "userApiKey": [] + } + ], + "parameters": [ + { + "$ref": "#/components/parameters/account_id" + } + ], + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "payload": { + "type": "array", + "description": "Array of inboxes", + "items": { + "$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" + } + } + } + } + } + } + }, + "/api/v1/accounts/{account_id}/inboxes/{id}/": { + "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" + } + } + } + } + } + } + }, + "/api/v1/accounts/{account_id}/inboxes/": { + "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" + } + } + } + } + } + } + }, + "/api/v1/accounts/{account_id}/inboxes/{id}": { + "patch": { + "tags": [ + "Inboxes" + ], + "operationId": "updateInbox", + "summary": "Update Inbox", + "security": [ + { + "userApiKey": [] + } + ], + "description": "Update an existing inbox", + "parameters": [ + { + "$ref": "#/components/parameters/account_id" + }, + { + "name": "id", + "in": "path", + "schema": { + "type": "number" + }, + "description": "ID of the inbox", + "required": true + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/inbox_update_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" + } + } + } + } + } + } + }, + "/api/v1/accounts/{account_id}/inboxes/{id}/agent_bot": { + "get": { + "tags": [ + "Inboxes" + ], + "operationId": "getInboxAgentBot", + "summary": "Show Inbox Agent Bot", + "description": "See if an agent bot is associated to the Inbox", + "security": [ + { + "userApiKey": [] + } + ], + "parameters": [ + { + "$ref": "#/components/parameters/account_id" + }, + { + "name": "id", + "in": "path", + "schema": { + "type": "number" + }, + "description": "ID of the inbox", + "required": true + } + ], + "responses": { + "204": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/agent_bot" + } + } + } + }, + "404": { + "description": "Inbox not found, Agent bot 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" + } + } + } + } + } + } + }, + "/api/v1/accounts/{account_id}/inboxes/{id}/set_agent_bot": { + "post": { + "tags": [ + "Inboxes" + ], + "operationId": "updateAgentBot", + "summary": "Add or remove agent bot", + "security": [ + { + "userApiKey": [] + } + ], + "description": "To add an agent bot pass agent_bot id, to remove agent bot from an inbox pass null", + "parameters": [ + { + "$ref": "#/components/parameters/account_id" + }, + { + "name": "id", + "in": "path", + "schema": { + "type": "number" + }, + "description": "ID of the inbox", + "required": true + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "agent_bot" + ], + "properties": { + "agent_bot": { + "type": "number", + "description": "Agent bot ID", + "example": 1 + } + } + } + } + } + }, + "responses": { + "204": { + "description": "Success" + }, + "404": { + "description": "Inbox not found, Agent bot 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" + } + } + } + } + } + } + }, + "/api/v1/accounts/{account_id}/inbox_members/{inbox_id}": { + "parameters": [ + { + "$ref": "#/components/parameters/account_id" + }, + { + "$ref": "#/components/parameters/inbox_id" + } + ], + "get": { + "tags": [ + "Inboxes" + ], + "operationId": "get-inbox-members", + "summary": "List Agents in Inbox", + "description": "Get Details of Agents in an Inbox", + "security": [ + { + "userApiKey": [] + } + ], + "parameters": [ + { + "$ref": "#/components/parameters/inbox_id" + } + ], + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "payload": { + "type": "array", + "description": "Array of all active agents", + "items": { + "$ref": "#/components/schemas/agent" + } + } + } + } + } + } + }, + "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" + } + } + } + } + } + } + }, + "/api/v1/accounts/{account_id}/inbox_members": { + "parameters": [ + { + "$ref": "#/components/parameters/account_id" + } + ], + "post": { + "tags": [ + "Inboxes" + ], + "operationId": "add-new-agent-to-inbox", + "summary": "Add a New Agent", + "description": "Add a new Agent to Inbox", + "security": [ + { + "userApiKey": [] + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "inbox_id", + "user_ids" + ], + "properties": { + "inbox_id": { + "type": "integer", + "description": "The ID of the inbox", + "example": 1 + }, + "user_ids": { + "type": "array", + "items": { + "type": "integer" + }, + "description": "IDs of users to be added to the inbox", + "example": [ + 1 + ] + } + } + } + } + } + }, + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "payload": { + "type": "array", + "description": "Array of all active agents", + "items": { + "$ref": "#/components/schemas/agent" + } + } + } + } + } + } + }, + "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" + } + } + } + }, + "422": { + "description": "User must exist", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/bad_request_error" + } + } + } + } + } + }, + "patch": { + "tags": [ + "Inboxes" + ], + "operationId": "update-agents-in-inbox", + "summary": "Update Agents in Inbox", + "description": "All agents except the one passed in params will be removed", + "security": [ + { + "userApiKey": [] + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "inbox_id", + "user_ids" + ], + "properties": { + "inbox_id": { + "type": "string", + "description": "The ID of the inbox", + "example": 1 + }, + "user_ids": { + "type": "array", + "items": { + "type": "integer" + }, + "description": "IDs of users to be added to the inbox", + "example": [ + 1 + ] + } + } + } + } + } + }, + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "payload": { + "type": "array", + "description": "Array of all active agents", + "items": { + "$ref": "#/components/schemas/agent" + } + } + } + } + } + } + }, + "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" + } + } + } + }, + "422": { + "description": "User must exist", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/bad_request_error" + } + } + } + } + } + }, + "delete": { + "tags": [ + "Inboxes" + ], + "operationId": "delete-agent-in-inbox", + "summary": "Remove an Agent from Inbox", + "description": "Remove an Agent from Inbox", + "security": [ + { + "userApiKey": [] + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "inbox_id", + "user_ids" + ], + "properties": { + "inbox_id": { + "type": "string", + "description": "The ID of the inbox" + }, + "user_ids": { + "type": "array", + "items": { + "type": "integer" + }, + "description": "IDs of users to be deleted from the inbox" + } + } + } + } + } + }, + "responses": { + "200": { + "description": "Success" + }, + "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" + } + } + } + }, + "422": { + "description": "User must exist", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/bad_request_error" + } + } + } + } + } + } + }, + "/api/v1/accounts/{account_id}/conversations/{conversation_id}/messages": { + "parameters": [ + { + "$ref": "#/components/parameters/account_id" + }, + { + "$ref": "#/components/parameters/conversation_id" + } + ], + "get": { + "tags": [ + "Messages" + ], + "operationId": "list-all-messages", + "summary": "Get messages", + "security": [ + { + "userApiKey": [] + } + ], + "description": "List all messages of a conversation", + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "meta": { + "type": "object", + "properties": { + "labels": { + "type": "array", + "items": { + "type": "string" + } + }, + "additional_attributes": { + "type": "object" + }, + "contact": { + "$ref": "#/components/schemas/contact" + }, + "assignee": { + "$ref": "#/components/schemas/agent" + }, + "agent_last_seen_at": { + "type": "string", + "format": "date-time" + }, + "assignee_last_seen_at": { + "type": "string", + "format": "date-time" + } + } + }, + "payload": { + "type": "array", + "description": "Array of messages", + "items": { + "$ref": "#/components/schemas/message" + } + } + } + } + } + } + }, + "404": { + "description": "Conversation not found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/bad_request_error" + } + } + } + }, + "401": { + "description": "Unauthorized", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/bad_request_error" + } + } + } + } + } + }, + "post": { + "tags": [ + "Messages" + ], + "operationId": "create-a-new-message-in-a-conversation", + "summary": "Create New Message", + "description": "Create a new message in the conversation", + "security": [ + { + "userApiKey": [] + }, + { + "agentBotApiKey": [] + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/conversation_message_create_payload" + } + } + } + }, + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "allOf": [ + { + "$ref": "#/components/schemas/generic_id" + }, + { + "$ref": "#/components/schemas/message" + } + ] + } + } + } + }, + "404": { + "description": "Conversation 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" + } + } + } + } + } + } + }, + "/api/v1/accounts/{account_id}/conversations/{conversation_id}/messages/{message_id}": { + "parameters": [ + { + "$ref": "#/components/parameters/account_id" + }, + { + "$ref": "#/components/parameters/conversation_id" + }, + { + "$ref": "#/components/parameters/message_id" + } + ], + "delete": { + "tags": [ + "Messages" + ], + "operationId": "delete-a-message", + "summary": "Delete a message", + "security": [ + { + "userApiKey": [] + } + ], + "description": "Delete a message and it's attachments from the conversation.", + "responses": { + "200": { + "description": "Success" + }, + "401": { + "description": "Unauthorized", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/bad_request_error" + } + } + } + }, + "404": { + "description": "The message or conversation does not exist in the account", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/bad_request_error" + } + } + } + } + } + } + }, + "/api/v1/accounts/{account_id}/integrations/apps": { + "parameters": [ + { + "$ref": "#/components/parameters/account_id" + } + ], + "get": { + "tags": [ + "Integrations" + ], + "operationId": "get-details-of-all-integrations", + "summary": "List all the Integrations", + "security": [ + { + "userApiKey": [] + } + ], + "description": "Get the details of all Integrations available for the account", + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "payload": { + "type": "array", + "description": "Array of Integration apps", + "items": { + "$ref": "#/components/schemas/integrations_app" + } + } + } + } + } + } + }, + "401": { + "description": "Unauthorized", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/bad_request_error" + } + } + } + }, + "404": { + "description": "Url not found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/bad_request_error" + } + } + } + } + } + } + }, + "/api/v1/accounts/{account_id}/integrations/hooks": { + "post": { + "tags": [ + "Integrations" + ], + "operationId": "create-an-integration-hook", + "summary": "Create an integration hook", + "description": "Create an integration hook", + "security": [ + { + "userApiKey": [] + } + ], + "parameters": [ + { + "$ref": "#/components/parameters/account_id" + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/integrations_hook_create_payload" + } + } + } + }, + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/integrations_hook" + } + } + } + }, + "401": { + "description": "Unauthorized", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/bad_request_error" + } + } + } + } + } + } + }, + "/api/v1/accounts/{account_id}/integrations/hooks/{hook_id}": { + "patch": { + "tags": [ + "Integrations" + ], + "operationId": "update-an-integrations-hook", + "summary": "Update an Integration Hook", + "description": "Update an Integration Hook", + "security": [ + { + "userApiKey": [] + } + ], + "parameters": [ + { + "$ref": "#/components/parameters/account_id" + }, + { + "$ref": "#/components/parameters/hook_id" + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/integrations_hook_update_payload" + } + } + } + }, + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/integrations_hook" + } + } + } + }, + "401": { + "description": "Unauthorized", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/bad_request_error" + } + } + } + } + } + }, + "delete": { + "tags": [ + "Integrations" + ], + "operationId": "delete-an-integration-hook", + "summary": "Delete an Integration Hook", + "description": "Delete an Integration Hook", + "security": [ + { + "userApiKey": [] + } + ], + "parameters": [ + { + "$ref": "#/components/parameters/account_id" + }, + { + "$ref": "#/components/parameters/hook_id" + } + ], + "responses": { + "200": { + "description": "Success" + }, + "401": { + "description": "Unauthorized", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/bad_request_error" + } + } + } + }, + "404": { + "description": "The hook does not exist in the account", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/bad_request_error" + } + } + } + } + } + } + }, + "/api/v1/profile": { + "get": { + "tags": [ + "Profile" + ], + "operationId": "fetchProfile", + "summary": "Fetch user profile", + "description": "Get the user profile details", + "security": [ + { + "userApiKey": [] + } + ], + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/user" + } + } + } + }, + "401": { + "description": "Unauthorized", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/bad_request_error" + } + } + } + } + } + } + }, + "/api/v1/accounts/{account_id}/teams": { + "parameters": [ + { + "$ref": "#/components/parameters/account_id" + } + ], + "get": { + "tags": [ + "Teams" + ], + "operationId": "list-all-teams", + "summary": "List all teams", + "security": [ + { + "userApiKey": [] + } + ], + "description": "List all teams available in the current account", + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "type": "array", + "description": "Array of teams", + "items": { + "$ref": "#/components/schemas/team" + } + } + } + } + }, + "401": { + "description": "Unauthorized", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/bad_request_error" + } + } + } + } + } + }, + "post": { + "tags": [ + "Teams" + ], + "operationId": "create-a-team", + "summary": "Create a team", + "security": [ + { + "userApiKey": [] + } + ], + "description": "Create a team in the account", + "parameters": [ + { + "$ref": "#/components/parameters/account_id" + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/team_create_update_payload" + } + } + } + }, + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/team" + } + } + } + }, + "401": { + "description": "Unauthorized", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/bad_request_error" + } + } + } + } + } + } + }, + "/api/v1/accounts/{account_id}/teams/{team_id}": { + "parameters": [ + { + "$ref": "#/components/parameters/account_id" + }, + { + "$ref": "#/components/parameters/team_id" + } + ], + "get": { + "tags": [ + "Teams" + ], + "operationId": "get-details-of-a-single-team", + "summary": "Get a team details", + "security": [ + { + "userApiKey": [] + } + ], + "description": "Get the details of a team in the account", + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/team" + } + } + } + }, + "401": { + "description": "Unauthorized", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/bad_request_error" + } + } + } + }, + "404": { + "description": "The given team ID does not exist in the account", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/bad_request_error" + } + } + } + } + } + }, + "patch": { + "tags": [ + "Teams" + ], + "operationId": "update-a-team", + "summary": "Update a team", + "security": [ + { + "userApiKey": [] + } + ], + "description": "Update a team's attributes", + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/team_create_update_payload" + } + } + } + }, + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/team" + } + } + } + }, + "401": { + "description": "Unauthorized", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/bad_request_error" + } + } + } + } + } + }, + "delete": { + "tags": [ + "Teams" + ], + "operationId": "delete-a-team", + "summary": "Delete a team", + "security": [ + { + "userApiKey": [] + } + ], + "description": "Delete a team from the account", + "responses": { + "200": { + "description": "Success" + }, + "401": { + "description": "Unauthorized", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/bad_request_error" + } + } + } + }, + "404": { + "description": "The team does not exist in the account", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/bad_request_error" + } + } + } + } + } + } + }, + "/api/v1/accounts/{account_id}/teams/{team_id}/team_members": { + "parameters": [ + { + "$ref": "#/components/parameters/account_id" + }, + { + "$ref": "#/components/parameters/team_id" + } + ], + "get": { + "tags": [ + "Teams" + ], + "operationId": "get-team-members", + "summary": "List Agents in Team", + "description": "Get Details of Agents in an Team", + "security": [ + { + "userApiKey": [] + } + ], + "parameters": [ + { + "$ref": "#/components/parameters/account_id" + }, + { + "$ref": "#/components/parameters/team_id" + } + ], + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "type": "array", + "description": "Array of all agents in the team", + "items": { + "$ref": "#/components/schemas/agent" + } + } + } + } + }, + "403": { + "description": "Access denied", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/bad_request_error" + } + } + } + }, + "404": { + "description": "Team not found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/bad_request_error" + } + } + } + } + } + }, + "post": { + "tags": [ + "Teams" + ], + "operationId": "add-new-agent-to-team", + "summary": "Add a New Agent", + "description": "Add a new Agent to Team", + "security": [ + { + "userApiKey": [] + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "user_ids" + ], + "properties": { + "user_ids": { + "type": "array", + "items": { + "type": "integer" + }, + "description": "IDs of users to be added to the team", + "example": [ + 1 + ] + } + } + } + } + } + }, + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "type": "array", + "description": "Array of all active agents", + "items": { + "$ref": "#/components/schemas/agent" + } + } + } + } + }, + "403": { + "description": "Access denied", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/bad_request_error" + } + } + } + }, + "404": { + "description": "Team not found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/bad_request_error" + } + } + } + }, + "422": { + "description": "User must exist", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/bad_request_error" + } + } + } + } + } + }, + "patch": { + "tags": [ + "Teams" + ], + "operationId": "update-agents-in-team", + "summary": "Update Agents in Team", + "description": "All agents except the one passed in params will be removed", + "security": [ + { + "userApiKey": [] + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "user_ids" + ], + "properties": { + "user_ids": { + "type": "array", + "items": { + "type": "integer" + }, + "description": "IDs of users to be added to the team", + "example": [ + 1 + ] + } + } + } + } + } + }, + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "type": "array", + "description": "Array of all agents in the team", + "items": { + "$ref": "#/components/schemas/agent" + } + } + } + } + }, + "403": { + "description": "Access denied", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/bad_request_error" + } + } + } + }, + "404": { + "description": "Team not found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/bad_request_error" + } + } + } + }, + "422": { + "description": "User must exist", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/bad_request_error" + } + } + } + } + } + }, + "delete": { + "tags": [ + "Teams" + ], + "operationId": "delete-agent-in-team", + "summary": "Remove an Agent from Team", + "description": "Remove an Agent from Team", + "security": [ + { + "userApiKey": [] + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "user_ids" + ], + "properties": { + "user_ids": { + "type": "array", + "items": { + "type": "integer" + }, + "description": "IDs of users to be deleted from the team" + } + } + } + } + } + }, + "responses": { + "200": { + "description": "Success" + }, + "403": { + "description": "Access denied", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/bad_request_error" + } + } + } + }, + "404": { + "description": "Team not found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/bad_request_error" + } + } + } + }, + "422": { + "description": "User must exist", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/bad_request_error" + } + } + } + } + } + } + }, + "/api/v1/accounts/{account_id}/custom_filters": { + "parameters": [ + { + "$ref": "#/components/parameters/account_id" + }, + { + "in": "query", + "name": "filter_type", + "schema": { + "type": "string", + "enum": [ + "conversation", + "contact", + "report" + ] + }, + "required": false, + "description": "The type of custom filter" + } + ], + "get": { + "tags": [ + "Custom Filters" + ], + "operationId": "list-all-filters", + "summary": "List all custom filters", + "description": "List all custom filters in a category of a user", + "security": [ + { + "userApiKey": [] + } + ], + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "type": "array", + "description": "Array of custom filters", + "items": { + "$ref": "#/components/schemas/custom_filter" + } + } + } + } + }, + "401": { + "description": "Unauthorized", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/bad_request_error" + } + } + } + } + } + }, + "post": { + "tags": [ + "Custom Filters" + ], + "operationId": "create-a-custom-filter", + "summary": "Create a custom filter", + "description": "Create a custom filter in the account", + "parameters": [ + { + "$ref": "#/components/parameters/account_id" + } + ], + "security": [ + { + "userApiKey": [] + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/custom_filter_create_update_payload" + } + } + } + }, + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/custom_filter" + } + } + } + }, + "401": { + "description": "Unauthorized", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/bad_request_error" + } + } + } + } + } + } + }, + "/api/v1/accounts/{account_id}/custom_filters/{custom_filter_id}": { + "parameters": [ + { + "$ref": "#/components/parameters/account_id" + }, + { + "$ref": "#/components/parameters/custom_filter_id" + } + ], + "get": { + "tags": [ + "Custom Filters" + ], + "operationId": "get-details-of-a-single-custom-filter", + "summary": "Get a custom filter details", + "description": "Get the details of a custom filter in the account", + "security": [ + { + "userApiKey": [] + } + ], + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/custom_filter" + } + } + } + }, + "401": { + "description": "Unauthorized", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/bad_request_error" + } + } + } + }, + "404": { + "description": "The given team ID does not exist in the account", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/bad_request_error" + } + } + } + } + } + }, + "patch": { + "tags": [ + "Custom Filters" + ], + "operationId": "update-a-custom-filter", + "summary": "Update a custom filter", + "security": [ + { + "userApiKey": [] + } + ], + "description": "Update a custom filter's attributes", + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/custom_filter_create_update_payload" + } + } + } + }, + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/custom_filter" + } + } + } + }, + "401": { + "description": "Unauthorized", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/bad_request_error" + } + } + } + } + } + }, + "delete": { + "tags": [ + "Custom Filters" + ], + "operationId": "delete-a-custom-filter", + "summary": "Delete a custom filter", + "security": [ + { + "userApiKey": [] + } + ], + "description": "Delete a custom filter from the account", + "responses": { + "200": { + "description": "Success" + }, + "401": { + "description": "Unauthorized", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/bad_request_error" + } + } + } + }, + "404": { + "description": "The custom filter does not exist in the account", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/bad_request_error" + } + } + } + } + } + } + }, + "/api/v1/accounts/{account_id}/webhooks": { + "parameters": [ + { + "$ref": "#/components/parameters/account_id" + } + ], + "get": { + "tags": [ + "Webhooks" + ], + "operationId": "list-all-webhooks", + "summary": "List all webhooks", + "security": [ + { + "userApiKey": [] + } + ], + "description": "List all webhooks in the account", + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "type": "array", + "description": "Array of webhook objects", + "items": { + "$ref": "#/components/schemas/webhook" + } + } + } + } + }, + "401": { + "description": "Unauthorized", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/bad_request_error" + } + } + } + } + } + }, + "post": { + "tags": [ + "Webhooks" + ], + "operationId": "create-a-webhook", + "summary": "Add a webhook", + "security": [ + { + "userApiKey": [] + } + ], + "description": "Add a webhook subscription to the account", + "parameters": [ + { + "$ref": "#/components/parameters/account_id" + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/webhook_create_update_payload" + } + } + } + }, + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/webhook" + } + } + } + }, + "401": { + "description": "Unauthorized", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/bad_request_error" + } + } + } + } + } + } + }, + "/api/v1/accounts/{account_id}/webhooks/{webhook_id}": { + "parameters": [ + { + "$ref": "#/components/parameters/account_id" + }, + { + "$ref": "#/components/parameters/webhook_id" + } + ], + "patch": { + "tags": [ + "Webhooks" + ], + "operationId": "update-a-webhook", + "summary": "Update a webhook object", + "security": [ + { + "userApiKey": [] + } + ], + "description": "Update a webhook object in the account", + "parameters": [ + { + "$ref": "#/components/parameters/account_id" + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/webhook_create_update_payload" + } + } + } + }, + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/webhook" + } + } + } + }, + "401": { + "description": "Unauthorized", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/bad_request_error" + } + } + } + } + } + }, + "delete": { + "tags": [ + "Webhooks" + ], + "operationId": "delete-a-webhook", + "summary": "Delete a webhook", + "security": [ + { + "userApiKey": [] + } + ], + "description": "Delete a webhook from the account", + "responses": { + "200": { + "description": "Success" + }, + "401": { + "description": "Unauthorized", + "content": { + "application/json": { + "schema": { + "type": "object" + } + } + } + }, + "404": { + "description": "The webhook does not exist in the account", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/bad_request_error" + } + } + } + } + } + } + }, + "/api/v2/accounts/{account_id}/reports": { + "parameters": [ + { + "$ref": "#/components/parameters/account_id" + }, + { + "$ref": "#/components/parameters/report_metric" + }, + { + "$ref": "#/components/parameters/report_type" + }, + { + "in": "query", + "name": "id", + "schema": { + "type": "string" + }, + "description": "The Id of specific object in case of agent/inbox/label" + }, + { + "in": "query", + "name": "since", + "schema": { + "type": "string" + }, + "description": "The timestamp from where report should start." + }, + { + "in": "query", + "name": "until", + "schema": { + "type": "string" + }, + "description": "The timestamp from where report should stop." + } + ], + "get": { + "tags": [ + "Reports" + ], + "operationId": "list-all-conversation-statistics", + "summary": "Get Account reports", + "security": [ + { + "userApiKey": [] + } + ], + "description": "Get Account reports for a specific type, metric and date range", + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "type": "array", + "description": "Array of date based conversation statistics", + "items": { + "type": "object", + "properties": { + "value": { + "type": "string" + }, + "timestamp": { + "type": "number" + } + } + } + } + } + } + }, + "404": { + "description": "reports 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" + } + } + } + } + } + } + }, + "/api/v2/accounts/{account_id}/reports/summary": { + "parameters": [ + { + "$ref": "#/components/parameters/account_id" + }, + { + "$ref": "#/components/parameters/report_type" + }, + { + "in": "query", + "name": "id", + "schema": { + "type": "string" + }, + "description": "The Id of specific object in case of agent/inbox/label" + }, + { + "in": "query", + "name": "since", + "schema": { + "type": "string" + }, + "description": "The timestamp from where report should start." + }, + { + "in": "query", + "name": "until", + "schema": { + "type": "string" + }, + "description": "The timestamp from where report should stop." + } + ], + "get": { + "tags": [ + "Reports" + ], + "operationId": "list-all-conversation-statistics-summary", + "summary": "Get Account reports summary", + "security": [ + { + "userApiKey": [] + } + ], + "description": "Get Account reports summary for a specific type and date range", + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/account_summary" + } + } + } + }, + "404": { + "description": "reports 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" + } + } + } + } + } + } + }, + "/api/v2/accounts/{account_id}/reports/conversations": { + "parameters": [ + { + "$ref": "#/components/parameters/account_id" + }, + { + "in": "query", + "name": "type", + "schema": { + "type": "string", + "enum": [ + "account" + ] + }, + "required": true, + "description": "Type of report" + } + ], + "get": { + "tags": [ + "Reports" + ], + "operationId": "get-account-conversation-metrics", + "summary": "Account Conversation Metrics", + "security": [ + { + "userApiKey": [] + } + ], + "description": "Get conversation metrics for Account", + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "type": "object", + "description": "Object of account conversation metrics", + "properties": { + "open": { + "type": "number" + }, + "unattended": { + "type": "number" + }, + "unassigned": { + "type": "number" + } + } + } + } + } + }, + "404": { + "description": "reports 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" + } + } + } + } + } + } + }, + "/api/v2/accounts/{account_id}/reports/conversations/": { + "parameters": [ + { + "$ref": "#/components/parameters/account_id" + }, + { + "in": "query", + "name": "type", + "schema": { + "type": "string", + "enum": [ + "agent" + ] + }, + "required": true, + "description": "Type of report" + }, + { + "in": "query", + "name": "user_id", + "schema": { + "type": "string" + }, + "description": "The numeric ID of the user" + } + ], + "get": { + "tags": [ + "Reports" + ], + "operationId": "get-agent-conversation-metrics", + "summary": "Agent Conversation Metrics", + "security": [ + { + "userApiKey": [] + } + ], + "description": "Get conversation metrics for Agent", + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "type": "array", + "description": "Array of agent based conversation metrics", + "items": { + "$ref": "#/components/schemas/agent_conversation_metrics" + } + } + } + } + }, + "404": { + "description": "reports 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" + } + } + } + } + } + } + } + }, + "components": { + "schemas": { + "bad_request_error": { + "title": "data", + "type": "object", + "properties": { + "description": { + "type": "string" + }, + "errors": { + "type": "array", + "items": { + "$ref": "#/components/schemas/request_error" + } + } + } + }, + "request_error": { + "type": "object", + "properties": { + "field": { + "type": "string" + }, + "message": { + "type": "string" + }, + "code": { + "type": "string" + } + } + }, + "generic_id": { + "type": "object", + "properties": { + "id": { + "type": "number" + } + } + }, + "canned_response": { + "type": "object", + "properties": { + "id": { + "type": "integer", + "description": "ID of the canned response" + }, + "account_id": { + "type": "integer", + "description": "Account Id" + }, + "short_code": { + "type": "string", + "description": "Short Code for quick access of the canned response" + }, + "content": { + "type": "string", + "description": "Message content for canned response" + }, + "created_at": { + "type": "string", + "description": "The date and time when the canned response was created" + }, + "updated_at": { + "type": "string", + "description": "The date and time when the canned response was updated" + } + } + }, + "custom_attribute": { + "type": "object", + "properties": { + "id": { + "type": "integer", + "description": "Identifier" + }, + "attribute_display_name": { + "type": "string", + "description": "Attribute display name" + }, + "attribute_display_type": { + "type": "string", + "description": "Attribute display type (text, number, currency, percent, link, date, list, checkbox)" + }, + "attribute_description": { + "type": "string", + "description": "Attribute description" + }, + "attribute_key": { + "type": "string", + "description": "Attribute unique key value" + }, + "regex_pattern": { + "type": "string", + "description": "Regex pattern" + }, + "regex_cue": { + "type": "string", + "description": "Regex cue" + }, + "attribute_values": { + "type": "string", + "description": "Attribute values" + }, + "attribute_model": { + "type": "string", + "description": "Attribute type(conversation_attribute/contact_attribute)" + }, + "default_value": { + "type": "string", + "description": "Attribute default value" + }, + "created_at": { + "type": "string", + "description": "The date and time when the custom attribute was created" + }, + "updated_at": { + "type": "string", + "description": "The date and time when the custom attribute was updated" + } + } + }, + "automation_rule": { + "type": "object", + "properties": { + "payload": { + "description": "Response payload that contains automation rule(s)", + "oneOf": [ + { + "type": "array", + "description": "Array of automation rules (for listing endpoint)", + "items": { + "$ref": "#/components/schemas/automation_rule_item" + } + }, + { + "type": "object", + "description": "Single automation rule (for show/create/update endpoints)", + "allOf": [ + { + "$ref": "#/components/schemas/automation_rule_item" + } + ] + } + ] + } + } + }, + "automation_rule_item": { + "type": "object", + "properties": { + "id": { + "type": "integer", + "description": "The ID of the automation rule" + }, + "account_id": { + "type": "integer", + "description": "Account Id" + }, + "name": { + "type": "string", + "description": "The name of the rule", + "example": "Add label on message create event" + }, + "description": { + "type": "string", + "description": "Description to give more context about the rule", + "example": "Add label support and sales on message create event if incoming message content contains text help" + }, + "event_name": { + "type": "string", + "description": "Automation Rule event, on which we call the actions(conversation_created, conversation_updated, message_created)", + "enum": [ + "conversation_created", + "conversation_updated", + "message_created" + ], + "example": "message_created" + }, + "conditions": { + "type": "array", + "description": "Array of conditions on which conversation/message filter would work", + "items": { + "type": "object", + "properties": { + "values": { + "type": "array", + "items": { + "type": "string" + } + }, + "attribute_key": { + "type": "string" + }, + "query_operator": { + "type": "string" + }, + "filter_operator": { + "type": "string" + } + }, + "example": { + "attribute_key": "content", + "filter_operator": "contains", + "values": [ + "help" + ], + "query_operator": "and" + } + } + }, + "actions": { + "type": "array", + "description": "Array of actions which we perform when condition matches", + "items": { + "type": "object", + "properties": { + "action_name": { + "type": "string" + }, + "action_params": { + "type": "array", + "items": { + "type": "string" + } + } + }, + "example": { + "action_name": "add_label", + "action_params": [ + "support", + "sales" + ] + } + } + }, + "created_on": { + "type": "integer", + "description": "The timestamp when the rule was created" + }, + "active": { + "type": "boolean", + "description": "Enable/disable automation rule" + } + } + }, + "portal": { + "type": "object", + "properties": { + "payload": { + "type": "array", + "items": { + "$ref": "#/components/schemas/portal_item" + } + } + } + }, + "portal_single": { + "type": "object", + "properties": { + "payload": { + "$ref": "#/components/schemas/portal_item" + } + } + }, + "portal_config": { + "type": "object", + "description": "Configuration settings for the portal", + "properties": { + "allowed_locales": { + "type": "array", + "description": "List of allowed locales for the portal", + "items": { + "type": "object", + "properties": { + "code": { + "type": "string", + "description": "The language code" + }, + "articles_count": { + "type": "integer", + "description": "Number of articles in this locale" + }, + "categories_count": { + "type": "integer", + "description": "Number of categories in this locale" + } + } + } + } + } + }, + "portal_logo": { + "type": "object", + "properties": { + "id": { + "type": "integer", + "description": "ID of the logo file" + }, + "portal_id": { + "type": "integer", + "description": "ID of the portal this logo belongs to" + }, + "file_type": { + "type": "string", + "description": "MIME type of the file" + }, + "account_id": { + "type": "integer", + "description": "ID of the account" + }, + "file_url": { + "type": "string", + "description": "URL to access the logo file" + }, + "blob_id": { + "type": "integer", + "description": "ID of the blob" + }, + "filename": { + "type": "string", + "description": "Name of the file" + } + } + }, + "portal_meta": { + "type": "object", + "properties": { + "all_articles_count": { + "type": "integer", + "description": "Total number of articles" + }, + "archived_articles_count": { + "type": "integer", + "nullable": true, + "description": "Number of archived articles" + }, + "published_count": { + "type": "integer", + "nullable": true, + "description": "Number of published articles" + }, + "draft_articles_count": { + "type": "integer", + "nullable": true, + "description": "Number of draft articles" + }, + "categories_count": { + "type": "integer", + "description": "Number of categories" + }, + "default_locale": { + "type": "string", + "description": "Default locale for the portal" + } + } + }, + "portal_item": { + "type": "object", + "properties": { + "id": { + "type": "integer", + "description": "The ID of the portal" + }, + "archived": { + "type": "boolean", + "description": "Whether the portal is archived" + }, + "color": { + "type": "string", + "description": "The color code for the portal" + }, + "config": { + "$ref": "#/components/schemas/portal_config" + }, + "custom_domain": { + "type": "string", + "description": "Custom domain for the portal" + }, + "header_text": { + "type": "string", + "description": "The header text for the portal" + }, + "homepage_link": { + "type": "string", + "description": "Homepage link for the portal" + }, + "name": { + "type": "string", + "description": "Name of the portal" + }, + "slug": { + "type": "string", + "description": "URL slug for the portal" + }, + "page_title": { + "type": "string", + "description": "Page title for the portal" + }, + "account_id": { + "type": "integer", + "description": "ID of the account the portal belongs to" + }, + "inbox": { + "$ref": "#/components/schemas/inbox" + }, + "logo": { + "$ref": "#/components/schemas/portal_logo" + }, + "meta": { + "$ref": "#/components/schemas/portal_meta" + } + } + }, + "category": { + "type": "object", + "properties": { + "id": { + "type": "integer" + }, + "description": { + "type": "string", + "description": "The text content." + }, + "locale": { + "type": "string" + }, + "name": { + "type": "string" + }, + "slug": { + "type": "string" + }, + "position": { + "type": "integer" + }, + "portal_id": { + "type": "integer" + }, + "account_id": { + "type": "integer" + }, + "associated_category_id": { + "type": "integer", + "description": "To associate similar categories to each other, e.g same category of product documentation in different languages" + }, + "parent_category_id": { + "type": "integer", + "description": "To define parent category, e.g product documentation has multiple level features in sales category or in engineering category." + } + } + }, + "article": { + "type": "object", + "properties": { + "id": { + "type": "integer" + }, + "content": { + "type": "string", + "description": "The text content." + }, + "meta": { + "type": "object" + }, + "position": { + "type": "integer" + }, + "status": { + "type": "integer", + "enum": [ + "draft", + "published", + "archived" + ] + }, + "title": { + "type": "string" + }, + "slug": { + "type": "string" + }, + "views": { + "type": "integer" + }, + "portal_id": { + "type": "integer" + }, + "account_id": { + "type": "integer" + }, + "author_id": { + "type": "integer" + }, + "category_id": { + "type": "integer" + }, + "folder_id": { + "type": "integer" + }, + "associated_article_id": { + "type": "integer", + "description": "To associate similar articles to each other, e.g to provide the link for the reference." + } + } + }, + "contact": { + "type": "object", + "properties": { + "payload": { + "type": "array", + "items": { + "type": "object", + "properties": { + "additional_attributes": { + "type": "object", + "description": "The object containing additional attributes related to the contact" + }, + "availability_status": { + "type": "string", + "description": "The availability status of the contact" + }, + "email": { + "type": "string", + "description": "The email address of the contact" + }, + "id": { + "type": "integer", + "description": "The ID of the contact" + }, + "name": { + "type": "string", + "description": "The name of the contact" + }, + "phone_number": { + "type": "string", + "description": "The phone number of the contact" + }, + "blocked": { + "type": "boolean", + "description": "Whether the contact is blocked" + }, + "identifier": { + "type": "string", + "description": "The identifier of the contact" + }, + "thumbnail": { + "type": "string", + "description": "The thumbnail of the contact" + }, + "custom_attributes": { + "type": "object", + "description": "The custom attributes of the contact", + "example": { + "attribute_key": "attribute_value", + "signed_up_at": "dd/mm/yyyy" + } + }, + "last_activity_at": { + "type": "integer", + "description": "The last activity at of the contact" + }, + "created_at": { + "type": "integer", + "description": "The created at of the contact" + }, + "contact_inboxes": { + "type": "array", + "items": { + "$ref": "#/components/schemas/contact_inboxes" + } + } + } + } + } + } + }, + "conversation": { + "type": "object", + "properties": { + "id": { + "type": "number", + "description": "ID of the conversation" + }, + "messages": { + "type": "array", + "items": { + "$ref": "#/components/schemas/message" + } + }, + "account_id": { + "type": "number", + "description": "Account Id" + }, + "uuid": { + "type": "string", + "description": "UUID of the conversation" + }, + "additional_attributes": { + "type": "object", + "description": "The object containing additional attributes related to the conversation" + }, + "agent_last_seen_at": { + "type": "number", + "description": "The last activity at of the agent" + }, + "assignee_last_seen_at": { + "type": "number", + "description": "The last activity at of the assignee" + }, + "can_reply": { + "type": "boolean", + "description": "Whether the conversation can be replied to" + }, + "contact_last_seen_at": { + "type": "number", + "description": "The last activity at of the contact" + }, + "custom_attributes": { + "type": "object", + "description": "The object to save custom attributes for conversation, accepts custom attributes key and value" + }, + "inbox_id": { + "type": "number", + "description": "ID of the inbox" + }, + "labels": { + "type": "array", + "items": { + "type": "string" + }, + "description": "The labels of the conversation" + }, + "muted": { + "type": "boolean", + "description": "Whether the conversation is muted" + }, + "snoozed_until": { + "type": "number", + "description": "The time at which the conversation will be unmuted" + }, + "status": { + "type": "string", + "enum": [ + "open", + "resolved", + "pending" + ], + "description": "The status of the conversation" + }, + "created_at": { + "type": "number", + "description": "The time at which conversation was created" + }, + "updated_at": { + "type": "number", + "description": "The time at which conversation was updated" + }, + "timestamp": { + "type": "string", + "description": "The time at which conversation was created" + }, + "first_reply_created_at": { + "type": "number", + "description": "The time at which the first reply was created" + }, + "unread_count": { + "type": "number", + "description": "The number of unread messages" + }, + "last_non_activity_message": { + "$ref": "#/components/schemas/message" + }, + "last_activity_at": { + "type": "number", + "description": "The last activity at of the conversation" + }, + "priority": { + "type": "string", + "description": "The priority of the conversation" + }, + "waiting_since": { + "type": "number", + "description": "The time at which the conversation was waiting" + }, + "sla_policy_id": { + "type": "number", + "description": "The ID of the SLA policy" + }, + "applied_sla": { + "type": "object", + "description": "The applied SLA" + }, + "sla_events": { + "type": "array", + "items": { + "type": "object", + "description": "SLA event objects" + } + } + } + }, + "message": { + "type": "object", + "properties": { + "id": { + "type": "number", + "description": "The ID of the message" + }, + "content": { + "type": "string", + "description": "The text content of the message" + }, + "account_id": { + "type": "number", + "description": "The ID of the account" + }, + "inbox_id": { + "type": "number", + "description": "The ID of the inbox" + }, + "conversation_id": { + "type": "number", + "description": "The ID of the conversation" + }, + "message_type": { + "type": "integer", + "enum": [ + 0, + 1, + 2 + ], + "description": "The type of the message" + }, + "created_at": { + "type": "integer", + "description": "The time at which message was created" + }, + "updated_at": { + "type": "integer", + "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" + ], + "description": "The status of the message" + }, + "source_id": { + "type": "string", + "description": "The source ID of the message" + }, + "content_type": { + "type": "string", + "enum": [ + "text", + "input_select", + "cards", + "form" + ], + "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" + ], + "description": "The type of the sender" + }, + "sender_id": { + "type": "number", + "description": "The ID of the sender" + }, + "external_source_ids": { + "type": "object", + "description": "The external source IDs of the message" + }, + "additional_attributes": { + "type": "object", + "description": "The additional attributes of the message" + }, + "processed_message_content": { + "type": "string", + "description": "The processed message content" + }, + "sentiment": { + "type": "object", + "description": "The sentiment of the message" + }, + "conversation": { + "type": "object", + "description": "The conversation object" + }, + "attachment": { + "type": "object", + "description": "The file object attached to the image" + }, + "sender": { + "type": "object", + "description": "User/Agent/AgentBot object" + } + } + }, + "user": { + "type": "object", + "properties": { + "id": { + "type": "number" + }, + "access_token": { + "type": "string" + }, + "account_id": { + "type": "number" + }, + "available_name": { + "type": "string" + }, + "avatar_url": { + "type": "string" + }, + "confirmed": { + "type": "boolean" + }, + "display_name": { + "type": "string", + "nullable": true + }, + "message_signature": { + "type": "string", + "nullable": true + }, + "email": { + "type": "string" + }, + "hmac_identifier": { + "type": "string" + }, + "inviter_id": { + "type": "number" + }, + "name": { + "type": "string" + }, + "provider": { + "type": "string" + }, + "pubsub_token": { + "type": "string" + }, + "role": { + "type": "string", + "enum": [ + "agent", + "administrator" + ] + }, + "ui_settings": { + "type": "object" + }, + "uid": { + "type": "string" + }, + "type": { + "type": "string", + "nullable": true + }, + "custom_attributes": { + "type": "object", + "description": "Available for users who are created through platform APIs and has custom attributes associated." + }, + "accounts": { + "type": "array", + "items": { + "type": "object", + "properties": { + "id": { + "type": "number" + }, + "name": { + "type": "string" + }, + "status": { + "type": "string" + }, + "active_at": { + "type": "string", + "format": "date-time" + }, + "role": { + "type": "string", + "enum": [ + "administrator", + "agent" + ] + }, + "permissions": { + "type": "array", + "items": { + "type": "string" + } + }, + "availability": { + "type": "string" + }, + "availability_status": { + "type": "string" + }, + "auto_offline": { + "type": "boolean" + }, + "custom_role_id": { + "type": "number", + "nullable": true + }, + "custom_role": { + "type": "object", + "nullable": true + } + } + } + } + } + }, + "agent": { + "type": "object", + "properties": { + "id": { + "type": "integer" + }, + "account_id": { + "type": "integer" + }, + "availability_status": { + "type": "string", + "enum": [ + "available", + "busy", + "offline" + ], + "description": "The availability status of the agent computed by Chatwoot." + }, + "auto_offline": { + "type": "boolean", + "description": "Whether the availability status of agent is configured to go offline automatically when away." + }, + "confirmed": { + "type": "boolean", + "description": "Whether the agent has confirmed their email address." + }, + "email": { + "type": "string", + "description": "The email of the agent" + }, + "available_name": { + "type": "string", + "description": "The available name of the agent" + }, + "name": { + "type": "string", + "description": "The name of the agent" + }, + "role": { + "type": "string", + "enum": [ + "agent", + "administrator" + ], + "description": "The role of the agent" + }, + "thumbnail": { + "type": "string", + "description": "The thumbnail of the agent" + }, + "custom_role_id": { + "type": "integer", + "description": "The custom role id of the agent" + } + } + }, + "inbox": { + "type": "object", + "properties": { + "id": { + "type": "number", + "description": "ID of the inbox" + }, + "name": { + "type": "string", + "description": "The name of the inbox" + }, + "website_url": { + "type": "string", + "description": "Website URL" + }, + "channel_type": { + "type": "string", + "description": "The type of the inbox" + }, + "avatar_url": { + "type": "string", + "description": "The avatar image of the inbox" + }, + "widget_color": { + "type": "string", + "description": "Widget Color used for customization of the widget" + }, + "website_token": { + "type": "string", + "description": "Website Token" + }, + "enable_auto_assignment": { + "type": "boolean", + "description": "The flag which shows whether Auto Assignment is enabled or not" + }, + "web_widget_script": { + "type": "string", + "description": "Script used to load the website widget" + }, + "welcome_title": { + "type": "string", + "description": "Welcome title to be displayed on the widget" + }, + "welcome_tagline": { + "type": "string", + "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", + "description": "A greeting message when the user starts the conversation" + }, + "channel_id": { + "type": "number", + "description": "ID of the channel this inbox belongs to" + }, + "working_hours_enabled": { + "type": "boolean", + "description": "The flag which shows whether working hours feature is enabled" + }, + "enable_email_collect": { + "type": "boolean", + "description": "The flag to enable collecting email from contacts" + }, + "csat_survey_enabled": { + "type": "boolean", + "description": "The flag to enable CSAT survey" + }, + "auto_assignment_config": { + "type": "object", + "description": "Configuration settings for auto assignment" + }, + "out_of_office_message": { + "type": "string", + "description": "Message to show when agents are out of office" + }, + "working_hours": { + "type": "array", + "description": "Configuration for working hours of the inbox", + "items": { + "type": "object", + "properties": { + "day_of_week": { + "type": "number", + "description": "Day of the week (0-6, where 0 is Sunday)" + }, + "closed_all_day": { + "type": "boolean", + "description": "Whether the inbox is closed for the entire day" + }, + "open_hour": { + "type": "number", + "description": "Hour when inbox opens (0-23)" + }, + "open_minutes": { + "type": "number", + "description": "Minutes of the hour when inbox opens (0-59)" + }, + "close_hour": { + "type": "number", + "description": "Hour when inbox closes (0-23)" + }, + "close_minutes": { + "type": "number", + "description": "Minutes of the hour when inbox closes (0-59)" + }, + "open_all_day": { + "type": "boolean", + "description": "Whether the inbox is open for the entire day" + } + } + } + }, + "timezone": { + "type": "string", + "description": "Timezone configuration for the inbox" + }, + "callback_webhook_url": { + "type": "string", + "description": "Webhook URL for callbacks" + }, + "allow_messages_after_resolved": { + "type": "boolean", + "description": "Whether to allow messages after a conversation is resolved" + }, + "lock_to_single_conversation": { + "type": "boolean", + "description": "Whether to lock a contact to a single conversation" + }, + "sender_name_type": { + "type": "string", + "description": "Type of sender name to display (e.g., friendly)" + }, + "business_name": { + "type": "string", + "description": "Business name associated with the inbox" + }, + "hmac_mandatory": { + "type": "boolean", + "description": "Whether HMAC verification is mandatory" + }, + "selected_feature_flags": { + "type": "object", + "description": "Selected feature flags for the inbox" + }, + "reply_time": { + "type": "string", + "description": "Expected reply time" + }, + "messaging_service_sid": { + "type": "string", + "description": "Messaging service SID for SMS providers" + }, + "phone_number": { + "type": "string", + "description": "Phone number associated with the inbox" + }, + "medium": { + "type": "string", + "description": "Medium of communication (e.g., sms, email)" + }, + "provider": { + "type": "string", + "description": "Provider of the channel" + } + } + }, + "inbox_contact": { + "type": "object", + "properties": { + "id": { + "type": "number", + "description": "ID of the inbox" + }, + "avatar_url": { + "type": "string", + "description": "The avatar image of the inbox" + }, + "channel_id": { + "type": "number", + "description": "The ID of the channel" + }, + "name": { + "type": "string", + "description": "The name of the inbox" + }, + "channel_type": { + "type": "string", + "description": "The type of the inbox" + }, + "provider": { + "type": "string", + "description": "The provider of the inbox" + } + } + }, + "agent_bot": { + "type": "object", + "properties": { + "id": { + "type": "number", + "description": "ID of the agent bot" + }, + "name": { + "type": "string", + "description": "The name of the agent bot" + }, + "description": { + "type": "string", + "description": "The description about the agent bot" + }, + "thumbnail": { + "type": "string", + "description": "The thumbnail of the agent bot" + }, + "outgoing_url": { + "type": "string", + "description": "The webhook URL for the bot" + }, + "bot_type": { + "type": "string", + "description": "The type of the bot" + }, + "bot_config": { + "type": "object", + "description": "The configuration of the bot" + }, + "account_id": { + "type": "number", + "description": "Account ID if it's an account specific bot" + }, + "access_token": { + "type": "string", + "description": "The access token for the bot" + }, + "system_bot": { + "type": "boolean", + "description": "Whether the bot is a system bot" + } + } + }, + "contact_inboxes": { + "type": "object", + "properties": { + "source_id": { + "type": "string", + "description": "Contact Inbox Source Id" + }, + "inbox": { + "$ref": "#/components/schemas/inbox_contact" + } + } + }, + "contactable_inboxes": { + "type": "object", + "properties": { + "source_id": { + "type": "string", + "description": "Contact Inbox Source Id" + }, + "inbox": { + "$ref": "#/components/schemas/inbox" + } + } + }, + "custom_filter": { + "type": "object", + "properties": { + "id": { + "type": "number", + "description": "The ID of the custom filter" + }, + "name": { + "type": "string", + "description": "The name of the custom filter" + }, + "type": { + "type": "string", + "enum": [ + "conversation", + "contact", + "report" + ], + "description": "The description about the custom filter" + }, + "query": { + "type": "object", + "description": "A query that needs to be saved as a custom filter" + }, + "created_at": { + "type": "string", + "format": "date-time", + "description": "The time at which the custom filter was created" + }, + "updated_at": { + "type": "string", + "format": "date-time", + "description": "The time at which the custom filter was updated" + } + } + }, + "webhook": { + "type": "object", + "properties": { + "id": { + "type": "number", + "description": "The ID of the webhook" + }, + "url": { + "type": "string", + "description": "The url to which the events will be send" + }, + "subscriptions": { + "type": "array", + "items": { + "type": "string", + "enum": [ + "conversation_created", + "conversation_status_changed", + "conversation_updated", + "contact_created", + "contact_updated", + "message_created", + "message_updated", + "webwidget_triggered" + ] + }, + "description": "The list of subscribed events" + }, + "account_id": { + "type": "number", + "description": "The id of the account which the webhook object belongs to" + } + } + }, + "account": { + "type": "object", + "properties": { + "id": { + "type": "number", + "description": "Account ID" + }, + "name": { + "type": "string", + "description": "Name of the account" + }, + "role": { + "type": "string", + "enum": [ + "administrator", + "agent" + ], + "description": "The user role in the account" + } + } + }, + "account_user": { + "type": "array", + "description": "Array of account users", + "items": { + "type": "object", + "properties": { + "account_id": { + "type": "integer", + "description": "The ID of the account" + }, + "user_id": { + "type": "integer", + "description": "The ID of the user" + }, + "role": { + "type": "string", + "description": "whether user is an administrator or agent" + } + } + } + }, + "platform_account": { + "type": "object", + "properties": { + "id": { + "type": "number", + "description": "Account ID" + }, + "name": { + "type": "string", + "description": "Name of the account" + } + } + }, + "team": { + "type": "object", + "properties": { + "id": { + "type": "number", + "description": "The ID of the team" + }, + "name": { + "type": "string", + "description": "The name of the team" + }, + "description": { + "type": "string", + "description": "The description about the team" + }, + "allow_auto_assign": { + "type": "boolean", + "description": "If this setting is turned on, the system would automatically assign the conversation to an agent in the team while assigning the conversation to a team" + }, + "account_id": { + "type": "number", + "description": "The ID of the account with the team is a part of" + }, + "is_member": { + "type": "boolean", + "description": "This field shows whether the current user is a part of the team" + } + } + }, + "integrations_app": { + "type": "object", + "properties": { + "id": { + "type": "string", + "description": "The ID of the integration" + }, + "name": { + "type": "string", + "description": "The name of the integration" + }, + "description": { + "type": "string", + "description": "The description about the team" + }, + "hook_type": { + "type": "string", + "description": "Whether the integration is an account or inbox integration" + }, + "enabled": { + "type": "boolean", + "description": "Whether the integration is enabled for the account" + }, + "allow_multiple_hooks": { + "type": "boolean", + "description": "Whether multiple hooks can be created for the integration" + }, + "hooks": { + "type": "array", + "items": { + "type": "object" + }, + "description": "If there are any hooks created for this integration" + } + } + }, + "integrations_hook": { + "type": "object", + "properties": { + "id": { + "type": "string", + "description": "The ID of the integration hook" + }, + "app_id": { + "type": "string", + "description": "The ID of the integration app" + }, + "inbox_id": { + "type": "string", + "description": "Inbox ID if its an Inbox integration" + }, + "account_id": { + "type": "string", + "description": "Account ID of the integration" + }, + "status": { + "type": "boolean", + "description": "Whether the integration hook is enabled for the account" + }, + "hook_type": { + "type": "boolean", + "description": "Whether its an account or inbox integration hook" + }, + "settings": { + "type": "object", + "description": "The associated settings for the integration" + } + } + }, + "public_contact": { + "type": "object", + "properties": { + "id": { + "type": "integer", + "description": "Id of the contact" + }, + "source_id": { + "type": "string", + "description": "The session identifier of the contact" + }, + "name": { + "type": "string", + "description": "Name of the contact" + }, + "email": { + "type": "string", + "description": "Email of the contact" + }, + "pubsub_token": { + "type": "string", + "description": "The token to be used to connect to chatwoot websocket" + } + } + }, + "public_conversation": { + "type": "object", + "properties": { + "id": { + "type": "integer", + "description": "Id of the conversation" + }, + "inbox_id": { + "type": "string", + "description": "The inbox id of the conversation" + }, + "messages": { + "type": "array", + "items": { + "$ref": "#/components/schemas/message" + }, + "description": "Messages in the conversation" + }, + "contact": { + "type": "object", + "description": "The contact information associated to the conversation" + } + } + }, + "public_message": { + "type": "object", + "properties": { + "id": { + "type": "string", + "description": "Id of the message" + }, + "content": { + "type": "string", + "description": "Text content of the message" + }, + "message_type": { + "type": "string", + "description": "Denotes the message type" + }, + "content_type": { + "type": "string", + "description": "Content type of the message" + }, + "content_attributes": { + "type": "string", + "description": "Additional content attributes of the message" + }, + "created_at": { + "type": "string", + "description": "Created at time stamp of the message" + }, + "conversation_id": { + "type": "string", + "description": "Conversation Id of the message" + }, + "attachments": { + "type": "array", + "items": { + "type": "object" + }, + "description": "Attachments if any" + }, + "sender": { + "type": "object", + "description": "Details of the sender" + } + } + }, + "public_inbox": { + "type": "object", + "properties": { + "identifier": { + "type": "string", + "description": "Inbox identifier" + }, + "name": { + "type": "string", + "description": "Name of the inbox" + }, + "timezone": { + "type": "string", + "description": "The timezone defined on the inbox" + }, + "working_hours": { + "type": "array", + "description": "The working hours defined on the inbox", + "items": { + "type": "object", + "properties": { + "day_of_week": { + "type": "integer", + "description": "Day of the week as a number. Sunday -> 0, Saturday -> 6" + }, + "open_all_day": { + "type": "boolean", + "description": "Whether or not the business is open the whole day" + }, + "closed_all_day": { + "type": "boolean", + "description": "Whether or not the business is closed the whole day" + }, + "open_hour": { + "type": "integer", + "description": "Opening hour. Can be null if closed all day" + }, + "open_minutes": { + "type": "integer", + "description": "Opening minute. Can be null if closed all day" + }, + "close_hour": { + "type": "integer", + "description": "Closing hour. Can be null if closed all day" + }, + "close_minutes": { + "type": "integer", + "description": "Closing minute. Can be null if closed all day" + } + } + } + }, + "working_hours_enabled": { + "type": "boolean", + "description": "Whether of not the working hours are enabled on the inbox" + }, + "csat_survey_enabled": { + "type": "boolean", + "description": "Whether of not the Customer Satisfaction survey is enabled on the inbox" + }, + "greeting_enabled": { + "type": "boolean", + "description": "Whether of not the Greeting Message is enabled on the inbox" + }, + "identity_validation_enabled": { + "type": "boolean", + "description": "Whether of not the User Identity Validation is enforced on the inbox" + } + } + }, + "account_create_update_payload": { + "type": "object", + "properties": { + "name": { + "type": "string", + "description": "Name of the account", + "example": "My Account" + }, + "locale": { + "type": "string", + "description": "The locale of the account", + "example": "en" + }, + "domain": { + "type": "string", + "description": "The domain of the account", + "example": "example.com" + }, + "support_email": { + "type": "string", + "description": "The support email of the account", + "example": "support@example.com" + }, + "status": { + "type": "string", + "enum": [ + "active", + "suspended" + ], + "description": "The status of the account", + "example": "active" + }, + "limits": { + "type": "object", + "description": "The limits of the account", + "example": {} + }, + "custom_attributes": { + "type": "object", + "description": "The custom attributes of the account", + "example": {} + } + } + }, + "account_user_create_update_payload": { + "type": "object", + "required": [ + "user_id", + "role" + ], + "properties": { + "user_id": { + "type": "integer", + "description": "The ID of the user", + "example": 1 + }, + "role": { + "type": "string", + "description": "whether user is an administrator or agent", + "example": "administrator" + } + } + }, + "platform_agent_bot_create_update_payload": { + "type": "object", + "properties": { + "name": { + "type": "string", + "description": "The name of the agent bot", + "example": "My Agent Bot" + }, + "description": { + "type": "string", + "description": "The description of the agent bot", + "example": "This is a sample agent bot" + }, + "outgoing_url": { + "type": "string", + "description": "The webhook URL for the bot", + "example": "https://example.com/webhook" + }, + "account_id": { + "type": "integer", + "description": "The account ID to associate the agent bot with", + "example": 1 + }, + "avatar": { + "type": "string", + "format": "binary", + "description": "Send the form data with the avatar image binary or use the avatar_url" + }, + "avatar_url": { + "type": "string", + "description": "The url to a jpeg, png file for the agent bot avatar", + "example": "https://example.com/avatar.png" + } + } + }, + "agent_bot_create_update_payload": { + "type": "object", + "properties": { + "name": { + "type": "string", + "description": "The name of the agent bot", + "example": "My Agent Bot" + }, + "description": { + "type": "string", + "description": "The description of the agent bot", + "example": "This is a sample agent bot" + }, + "outgoing_url": { + "type": "string", + "description": "The webhook URL for the bot", + "example": "https://example.com/webhook" + }, + "avatar": { + "type": "string", + "format": "binary", + "description": "Send the form data with the avatar image binary or use the avatar_url" + }, + "avatar_url": { + "type": "string", + "description": "The url to a jpeg, png file for the agent bot avatar", + "example": "https://example.com/avatar.png" + }, + "bot_type": { + "type": "integer", + "description": "The type of the bot (0 for webhook)", + "example": 0 + }, + "bot_config": { + "type": "object", + "description": "The configuration for the bot", + "example": {} + } + } + }, + "user_create_update_payload": { + "type": "object", + "properties": { + "name": { + "type": "string", + "description": "Name of the user", + "example": "Daniel" + }, + "display_name": { + "type": "string", + "description": "Display name of the user", + "example": "Dan" + }, + "email": { + "type": "string", + "description": "Email of the user", + "example": "daniel@acme.inc" + }, + "password": { + "type": "string", + "description": "Password must contain uppercase, lowercase letters, number and a special character", + "example": "Password2!" + }, + "custom_attributes": { + "type": "object", + "description": "Custom attributes you want to associate with the user", + "example": {} + } + } + }, + "canned_response_create_update_payload": { + "type": "object", + "properties": { + "content": { + "type": "string", + "description": "Message content for canned response", + "example": "Hello, {{contact.name}}! Welcome to our service." + }, + "short_code": { + "type": "string", + "description": "Short Code for quick access of the canned response", + "example": "welcome" + } + } + }, + "custom_attribute_create_update_payload": { + "type": "object", + "properties": { + "attribute_display_name": { + "type": "string", + "description": "Attribute display name", + "example": "Custom Attribute" + }, + "attribute_display_type": { + "type": "integer", + "description": "Attribute display type (text- 0, number- 1, currency- 2, percent- 3, link- 4, date- 5, list- 6, checkbox- 7)", + "example": 0 + }, + "attribute_description": { + "type": "string", + "description": "Attribute description", + "example": "This is a custom attribute" + }, + "attribute_key": { + "type": "string", + "description": "Attribute unique key value", + "example": "custom_attribute" + }, + "attribute_values": { + "type": "array", + "description": "Attribute values", + "items": { + "type": "string" + }, + "example": [ + "value1", + "value2" + ] + }, + "attribute_model": { + "type": "integer", + "description": "Attribute type(conversation_attribute- 0, contact_attribute- 1)", + "example": 0 + }, + "regex_pattern": { + "type": "string", + "description": "Regex pattern (Only applicable for type- text). The regex pattern is used to validate the attribute value(s).", + "example": "^[a-zA-Z0-9]+$" + }, + "regex_cue": { + "type": "string", + "description": "Regex cue message (Only applicable for type- text). The cue message is shown when the regex pattern is not matched.", + "example": "Please enter a valid value" + } + } + }, + "agent_create_payload": { + "type": "object", + "required": [ + "name", + "email", + "role" + ], + "properties": { + "name": { + "type": "string", + "description": "Full Name of the agent", + "example": "John Doe" + }, + "email": { + "type": "string", + "description": "Email of the Agent", + "example": "john.doe@acme.inc" + }, + "role": { + "type": "string", + "enum": [ + "agent", + "administrator" + ], + "description": "Whether its administrator or agent", + "example": "agent" + }, + "availability_status": { + "type": "string", + "enum": [ + "available", + "busy", + "offline" + ], + "description": "The availability setting of the agent.", + "example": "available" + }, + "auto_offline": { + "type": "boolean", + "description": "Whether the availability status of agent is configured to go offline automatically when away.", + "example": true + } + } + }, + "agent_update_payload": { + "type": "object", + "required": [ + "role" + ], + "properties": { + "role": { + "type": "string", + "enum": [ + "agent", + "administrator" + ], + "description": "Whether its administrator or agent", + "example": "agent" + }, + "availability_status": { + "type": "string", + "enum": [ + "available", + "busy", + "offline" + ], + "description": "The availability status of the agent.", + "example": "available" + }, + "auto_offline": { + "type": "boolean", + "description": "Whether the availability status of agent is configured to go offline automatically when away.", + "example": true + } + } + }, + "contact_create_payload": { + "type": "object", + "required": [ + "inbox_id" + ], + "properties": { + "inbox_id": { + "type": "number", + "description": "ID of the inbox to which the contact belongs", + "example": 1 + }, + "name": { + "type": "string", + "description": "name of the contact", + "example": "Alice" + }, + "email": { + "type": "string", + "description": "email of the contact", + "example": "alice@acme.inc" + }, + "blocked": { + "type": "boolean", + "description": "whether the contact is blocked or not", + "example": false + }, + "phone_number": { + "type": "string", + "description": "phone number of the contact", + "example": "+123456789" + }, + "avatar": { + "type": "string", + "format": "binary", + "description": "Send the form data with the avatar image binary or use the avatar_url" + }, + "avatar_url": { + "type": "string", + "description": "The url to a jpeg, png file for the contact avatar", + "example": "https://example.com/avatar.png" + }, + "identifier": { + "type": "string", + "description": "A unique identifier for the contact in external system", + "example": "1234567890" + }, + "additional_attributes": { + "type": "object", + "description": "An object where you can store additional attributes for contact. example {\"type\":\"customer\", \"age\":30}", + "example": { + "type": "customer", + "age": 30 + } + }, + "custom_attributes": { + "type": "object", + "description": "An object where you can store custom attributes for contact. example {\"type\":\"customer\", \"age\":30}, this should have a valid custom attribute definition.", + "example": {} + } + } + }, + "contact_update_payload": { + "type": "object", + "properties": { + "name": { + "type": "string", + "description": "name of the contact", + "example": "Alice" + }, + "email": { + "type": "string", + "description": "email of the contact", + "example": "alice@acme.inc" + }, + "blocked": { + "type": "boolean", + "description": "whether the contact is blocked or not", + "example": false + }, + "phone_number": { + "type": "string", + "description": "phone number of the contact", + "example": "+123456789" + }, + "avatar": { + "type": "string", + "format": "binary", + "description": "Send the form data with the avatar image binary or use the avatar_url" + }, + "avatar_url": { + "type": "string", + "description": "The url to a jpeg, png file for the contact avatar", + "example": "https://example.com/avatar.png" + }, + "identifier": { + "type": "string", + "description": "A unique identifier for the contact in external system", + "example": "1234567890" + }, + "additional_attributes": { + "type": "object", + "description": "An object where you can store additional attributes for contact. example {\"type\":\"customer\", \"age\":30}", + "example": { + "type": "customer", + "age": 30 + } + }, + "custom_attributes": { + "type": "object", + "description": "An object where you can store custom attributes for contact. example {\"type\":\"customer\", \"age\":30}, this should have a valid custom attribute definition.", + "example": {} + } + } + }, + "conversation_create_payload": { + "type": "object", + "required": [ + "source_id", + "inbox_id" + ], + "properties": { + "source_id": { + "type": "string", + "description": "Conversation source id", + "example": "1234567890" + }, + "inbox_id": { + "type": "integer", + "description": "Id of inbox in which the conversation is created Allowed Inbox Types: Website, Phone, Api, Email", + "example": 1 + }, + "contact_id": { + "type": "integer", + "description": "Contact Id for which conversation is created", + "example": 1 + }, + "additional_attributes": { + "type": "object", + "description": "Lets you specify attributes like browser information", + "example": { + "browser": "Chrome", + "browser_version": "89.0.4389.82", + "os": "Windows", + "os_version": "10" + } + }, + "custom_attributes": { + "type": "object", + "description": "The object to save custom attributes for conversation, accepts custom attributes key and value", + "example": { + "attribute_key": "attribute_value", + "priority_conversation_number": 3 + } + }, + "status": { + "type": "string", + "enum": [ + "open", + "resolved", + "pending" + ], + "description": "Specify the conversation whether it's pending, open, closed", + "example": "open" + }, + "assignee_id": { + "type": "integer", + "description": "Agent Id for assigning a conversation to an agent", + "example": 1 + }, + "team_id": { + "type": "integer", + "description": "Team Id for assigning a conversation to a team\\", + "example": 1 + }, + "snoozed_until": { + "type": "string", + "format": "date-time", + "description": "Snoozed until date time", + "example": "2030-07-21T17:32:28Z" + }, + "message": { + "type": "object", + "description": "The initial message to be sent to the conversation", + "required": [ + "content" + ], + "properties": { + "content": { + "type": "string", + "description": "The content of the message", + "example": "Hello, how can I help you?" + }, + "template_params": { + "type": "object", + "description": "The template params for the message in case of whatsapp Channel", + "properties": { + "name": { + "type": "string", + "description": "Name of the template", + "example": "sample_issue_resolution" + }, + "category": { + "type": "string", + "description": "Category of the template", + "example": "UTILITY" + }, + "language": { + "type": "string", + "description": "Language of the template", + "example": "en_US" + }, + "processed_params": { + "type": "object", + "description": "The processed param values for template variables in template", + "example": { + "1": "Chatwoot" + } + } + } + } + } + } + } + }, + "conversation_message_create_payload": { + "type": "object", + "required": [ + "content" + ], + "properties": { + "content": { + "type": "string", + "description": "The content of the message", + "example": "Hello, how can I help you?" + }, + "message_type": { + "type": "string", + "enum": [ + "outgoing", + "incoming" + ], + "description": "The type of the message", + "example": "outgoing" + }, + "private": { + "type": "boolean", + "description": "Flag to identify if it is a private note", + "example": false + }, + "content_type": { + "type": "string", + "enum": [ + "text", + "input_email", + "cards", + "input_select", + "form", + "article" + ], + "description": "Content type of the message", + "example": "text" + }, + "content_attributes": { + "type": "object", + "description": "Attributes based on the content type", + "example": {} + }, + "campaign_id": { + "type": "integer", + "description": "The campaign id to which the message belongs", + "example": 1 + }, + "template_params": { + "type": "object", + "description": "The template params for the message in case of whatsapp Channel", + "properties": { + "name": { + "type": "string", + "description": "Name of the template", + "example": "sample_issue_resolution" + }, + "category": { + "type": "string", + "description": "Category of the template", + "example": "UTILITY" + }, + "language": { + "type": "string", + "description": "Language of the template", + "example": "en_US" + }, + "processed_params": { + "type": "object", + "description": "The processed param values for template variables in template", + "example": { + "1": "Chatwoot" + } + } + } + } + } + }, + "inbox_create_payload": { + "type": "object", + "properties": { + "name": { + "type": "string", + "description": "The name of the inbox", + "example": "Support" + }, + "avatar": { + "type": "string", + "format": "binary", + "description": "Image file for avatar" + }, + "greeting_enabled": { + "type": "boolean", + "description": "Enable greeting message", + "example": true + }, + "greeting_message": { + "type": "string", + "description": "Greeting message to be displayed on the widget", + "example": "Hello, how can I help you?" + }, + "enable_email_collect": { + "type": "boolean", + "description": "Enable email collection", + "example": true + }, + "csat_survey_enabled": { + "type": "boolean", + "description": "Enable CSAT survey", + "example": true + }, + "enable_auto_assignment": { + "type": "boolean", + "description": "Enable Auto Assignment", + "example": true + }, + "working_hours_enabled": { + "type": "boolean", + "description": "Enable working hours", + "example": true + }, + "out_of_office_message": { + "type": "string", + "description": "Out of office message to be displayed on the widget", + "example": "We are currently out of office. Please leave a message and we will get back to you." + }, + "timezone": { + "type": "string", + "description": "Timezone of the inbox", + "example": "America/New_York" + }, + "allow_messages_after_resolved": { + "type": "boolean", + "description": "Allow messages after conversation is resolved", + "example": true + }, + "lock_to_single_conversation": { + "type": "boolean", + "description": "Lock to single conversation", + "example": true + }, + "portal_id": { + "type": "integer", + "description": "Id of the help center portal to attach to the inbox", + "example": 1 + }, + "sender_name_type": { + "type": "string", + "description": "Sender name type for the inbox", + "enum": [ + "friendly", + "professional" + ], + "example": "friendly" + }, + "business_name": { + "type": "string", + "description": "Business name for the inbox", + "example": "My Business" + }, + "channel": { + "type": "object", + "properties": { + "type": { + "type": "string", + "description": "Type of the channel", + "enum": [ + "web_widget", + "api", + "email", + "line", + "telegram", + "whatsapp", + "sms" + ], + "example": "web_widget" + }, + "website_url": { + "type": "string", + "description": "URL at which the widget will be loaded", + "example": "https://example.com" + }, + "welcome_title": { + "type": "string", + "description": "Welcome title to be displayed on the widget", + "example": "Welcome to our support" + }, + "welcome_tagline": { + "type": "string", + "description": "Welcome tagline to be displayed on the widget", + "example": "We are here to help you" + }, + "widget_color": { + "type": "string", + "description": "A Hex-color string used to customize the widget", + "example": "#FF5733" + } + } + } + } + }, + "inbox_update_payload": { + "type": "object", + "properties": { + "name": { + "type": "string", + "description": "The name of the inbox", + "example": "Support" + }, + "avatar": { + "type": "string", + "format": "binary", + "description": "Image file for avatar" + }, + "greeting_enabled": { + "type": "boolean", + "description": "Enable greeting message", + "example": true + }, + "greeting_message": { + "type": "string", + "description": "Greeting message to be displayed on the widget", + "example": "Hello, how can I help you?" + }, + "enable_email_collect": { + "type": "boolean", + "description": "Enable email collection", + "example": true + }, + "csat_survey_enabled": { + "type": "boolean", + "description": "Enable CSAT survey", + "example": true + }, + "enable_auto_assignment": { + "type": "boolean", + "description": "Enable Auto Assignment", + "example": true + }, + "working_hours_enabled": { + "type": "boolean", + "description": "Enable working hours", + "example": true + }, + "out_of_office_message": { + "type": "string", + "description": "Out of office message to be displayed on the widget", + "example": "We are currently out of office. Please leave a message and we will get back to you." + }, + "timezone": { + "type": "string", + "description": "Timezone of the inbox", + "example": "America/New_York" + }, + "allow_messages_after_resolved": { + "type": "boolean", + "description": "Allow messages after conversation is resolved", + "example": true + }, + "lock_to_single_conversation": { + "type": "boolean", + "description": "Lock to single conversation", + "example": true + }, + "portal_id": { + "type": "integer", + "description": "Id of the help center portal to attach to the inbox", + "example": 1 + }, + "sender_name_type": { + "type": "string", + "description": "Sender name type for the inbox", + "enum": [ + "friendly", + "professional" + ], + "example": "friendly" + }, + "business_name": { + "type": "string", + "description": "Business name for the inbox", + "example": "My Business" + }, + "channel": { + "type": "object", + "properties": { + "website_url": { + "type": "string", + "description": "URL at which the widget will be loaded", + "example": "https://example.com" + }, + "welcome_title": { + "type": "string", + "description": "Welcome title to be displayed on the widget", + "example": "Welcome to our support" + }, + "welcome_tagline": { + "type": "string", + "description": "Welcome tagline to be displayed on the widget", + "example": "We are here to help you" + }, + "widget_color": { + "type": "string", + "description": "A Hex-color string used to customize the widget", + "example": "#FF5733" + } + } + } + } + }, + "team_create_update_payload": { + "type": "object", + "properties": { + "name": { + "type": "string", + "description": "The name of the team", + "example": "Support Team" + }, + "description": { + "type": "string", + "description": "The description of the team", + "example": "This is a team of support agents" + }, + "allow_auto_assign": { + "type": "boolean", + "description": "If this setting is turned on, the system would automatically assign the conversation to an agent in the team while assigning the conversation to a team", + "example": true + } + } + }, + "custom_filter_create_update_payload": { + "type": "object", + "properties": { + "name": { + "type": "string", + "description": "The name of the custom filter", + "example": "My Custom Filter" + }, + "type": { + "type": "string", + "enum": [ + "conversation", + "contact", + "report" + ], + "description": "The description about the custom filter", + "example": "conversation" + }, + "query": { + "type": "object", + "description": "A query that needs to be saved as a custom filter", + "example": {} + } + } + }, + "webhook_create_update_payload": { + "type": "object", + "properties": { + "url": { + "type": "string", + "description": "The url where the events should be sent", + "example": "https://example.com/webhook" + }, + "subscriptions": { + "type": "array", + "items": { + "type": "string", + "enum": [ + "conversation_created", + "conversation_status_changed", + "conversation_updated", + "message_created", + "message_updated", + "contact_created", + "contact_updated", + "webwidget_triggered" + ] + }, + "description": "The events you want to subscribe to.", + "example": [ + "conversation_created", + "conversation_status_changed" + ] + } + } + }, + "integrations_hook_create_payload": { + "type": "object", + "properties": { + "app_id": { + "type": "integer", + "description": "The ID of app for which integration hook is being created", + "example": 1 + }, + "inbox_id": { + "type": "integer", + "description": "The inbox ID, if the hook is an inbox hook", + "example": 1 + }, + "status": { + "type": "integer", + "description": "The status of the integration (0 for inactive, 1 for active)", + "example": 1 + }, + "settings": { + "type": "object", + "description": "The settings required by the integration", + "example": {} + } + } + }, + "integrations_hook_update_payload": { + "type": "object", + "properties": { + "status": { + "type": "integer", + "description": "The status of the integration (0 for inactive, 1 for active)", + "example": 1 + }, + "settings": { + "type": "object", + "description": "The settings required by the integration", + "example": {} + } + } + }, + "automation_rule_create_update_payload": { + "type": "object", + "properties": { + "name": { + "type": "string", + "description": "Rule name", + "example": "Add label on message create event" + }, + "description": { + "type": "string", + "description": "The description about the automation and actions", + "example": "Add label support and sales on message create event if incoming message content contains text help" + }, + "event_name": { + "type": "string", + "enum": [ + "conversation_created", + "conversation_updated", + "message_created" + ], + "example": "message_created", + "description": "The event when you want to execute the automation actions" + }, + "active": { + "type": "boolean", + "description": "Enable/disable automation rule" + }, + "actions": { + "type": "array", + "description": "Array of actions which you want to perform when condition matches, e.g add label support if message contains content help.", + "items": { + "type": "object", + "example": { + "action_name": "add_label", + "action_params": [ + "support" + ] + } + } + }, + "conditions": { + "type": "array", + "description": "Array of conditions on which conversation filter would work, e.g message content contains text help.", + "items": { + "type": "object", + "example": { + "attribute_key": "content", + "filter_operator": "contains", + "query_operator": "OR", + "values": [ + "help" + ] + } + } + } + } + }, + "portal_create_update_payload": { + "type": "object", + "properties": { + "color": { + "type": "string", + "description": "Header color for help-center in hex format", + "example": "#FFFFFF" + }, + "custom_domain": { + "type": "string", + "description": "Custom domain to display help center.", + "example": "chatwoot.help" + }, + "header_text": { + "type": "string", + "description": "Help center header", + "example": "Handbook" + }, + "homepage_link": { + "type": "string", + "description": "link to main dashboard", + "example": "https://www.chatwoot.com/" + }, + "name": { + "type": "string", + "description": "Name for the portal", + "example": "Handbook" + }, + "page_title": { + "type": "string", + "description": "Page title for the portal", + "example": "Handbook" + }, + "slug": { + "type": "string", + "description": "Slug for the portal to display in link", + "example": "handbook" + }, + "archived": { + "type": "boolean", + "description": "Status to check if portal is live", + "example": false + }, + "config": { + "type": "object", + "description": "Configuration about supporting locales", + "example": { + "allowed_locales": [ + "en", + "es" + ], + "default_locale": "en" + } + } + } + }, + "category_create_update_payload": { + "type": "object", + "properties": { + "name": { + "type": "string", + "description": "The name of the category", + "example": "Category Name" + }, + "description": { + "type": "string", + "description": "A description for the category", + "example": "Category description" + }, + "position": { + "type": "integer", + "description": "Category position in the portal list to sort", + "example": 1 + }, + "slug": { + "type": "string", + "description": "The category slug used in the URL", + "example": "category-name" + }, + "locale": { + "type": "string", + "description": "The locale of the category", + "example": "en" + }, + "icon": { + "type": "string", + "description": "The icon of the category as a string (emoji)", + "example": "📚" + }, + "parent_category_id": { + "type": "integer", + "description": "To define parent category, e.g product documentation has multiple level features in sales category or in engineering category.", + "example": 1 + }, + "associated_category_id": { + "type": "integer", + "description": "To associate similar categories to each other, e.g same category of product documentation in different languages", + "example": 2 + } + } + }, + "article_create_update_payload": { + "type": "object", + "properties": { + "title": { + "type": "string", + "description": "The title of the article", + "example": "Article Title" + }, + "slug": { + "type": "string", + "description": "The slug of the article", + "example": "article-title" + }, + "position": { + "type": "integer", + "description": "article position in category", + "example": 1 + }, + "content": { + "type": "string", + "description": "The text content.", + "example": "This is the content of the article" + }, + "description": { + "type": "string", + "description": "The description of the article", + "example": "This is the description of the article" + }, + "category_id": { + "type": "integer", + "description": "The category id of the article", + "example": 1 + }, + "author_id": { + "type": "integer", + "description": "The author agent id of the article", + "example": 1 + }, + "associated_article_id": { + "type": "integer", + "description": "To associate similar articles to each other, e.g to provide the link for the reference.", + "example": 2 + }, + "status": { + "type": "integer", + "description": "The status of the article. 0 for draft, 1 for published, 2 for archived", + "example": 1 + }, + "locale": { + "type": "string", + "description": "The locale of the article", + "example": "en" + }, + "meta": { + "type": "object", + "description": "Use for search", + "example": { + "tags": [ + "article_name" + ], + "title": "article title", + "description": "article description" + } + } + } + }, + "public_contact_create_update_payload": { + "type": "object", + "properties": { + "identifier": { + "type": "string", + "description": "External identifier of the contact", + "example": "1234567890" + }, + "identifier_hash": { + "type": "string", + "description": "Identifier hash prepared for HMAC authentication", + "example": "e93275d4eba0e5679ad55f5360af00444e2a888df9b0afa3e8b691c3173725f9" + }, + "email": { + "type": "string", + "description": "Email of the contact", + "example": "alice@acme.inc" + }, + "name": { + "type": "string", + "description": "Name of the contact", + "example": "Alice" + }, + "phone_number": { + "type": "string", + "description": "Phone number of the contact", + "example": "+123456789" + }, + "avatar": { + "type": "string", + "format": "binary", + "description": "Send the form data with the avatar image binary or use the avatar_url" + }, + "custom_attributes": { + "type": "object", + "description": "Custom attributes of the customer", + "example": {} + } + } + }, + "public_message_create_payload": { + "type": "object", + "properties": { + "content": { + "type": "string", + "description": "Content for the message", + "example": "Hello, how can I help you?" + }, + "echo_id": { + "type": "string", + "description": "Temporary identifier which will be passed back via websockets", + "example": "1234567890" + } + } + }, + "public_message_update_payload": { + "type": "object", + "properties": { + "submitted_values": { + "type": "object", + "description": "Replies to the Bot Message Types", + "properties": { + "name": { + "type": "string", + "description": "The name of the submiitted value", + "example": "My Name" + }, + "title": { + "type": "string", + "description": "The title of the submitted value", + "example": "My Title" + }, + "value": { + "type": "string", + "description": "The value of the submitted value", + "example": "value" + }, + "csat_survey_response": { + "type": "object", + "description": "The CSAT survey response", + "properties": { + "feedback_message": { + "type": "string", + "description": "The feedback message of the CSAT survey response", + "example": "Great service!" + }, + "rating": { + "type": "integer", + "description": "The rating of the CSAT survey response", + "example": 5 + } + } + } + } + } + } + }, + "public_conversation_create_payload": { + "type": "object", + "properties": { + "custom_attributes": { + "type": "object", + "description": "Custom attributes of the conversation", + "example": {} + } + } + }, + "extended_contact": { + "allOf": [ + { + "$ref": "#/components/schemas/contact" + }, + { + "type": "object", + "properties": { + "id": { + "type": "number", + "description": "Id of the user" + }, + "availability_status": { + "type": "string", + "enum": [ + "online", + "offline" + ], + "description": "Availability status of the user" + } + } + } + ] + }, + "contact_base": { + "allOf": [ + { + "$ref": "#/components/schemas/generic_id" + }, + { + "$ref": "#/components/schemas/contact" + } + ] + }, + "contact_list": { + "type": "array", + "description": "array of contacts", + "items": { + "allOf": [ + { + "$ref": "#/components/schemas/contact" + } + ] + } + }, + "contact_conversations": { + "type": "array", + "description": "array of conversations", + "items": { + "allOf": [ + { + "$ref": "#/components/schemas/conversation" + }, + { + "type": "object", + "properties": { + "meta": { + "type": "object", + "properties": { + "sender": { + "type": "object", + "properties": { + "additional_attributes": { + "type": "object", + "description": "The additional attributes of the sender" + }, + "availability_status": { + "type": "string", + "description": "The availability status of the sender" + }, + "email": { + "type": "string", + "description": "The email of the sender" + }, + "id": { + "type": "number", + "description": "ID fo the sender" + }, + "name": { + "type": "string", + "description": "The name of the sender" + }, + "phone_number": { + "type": "string", + "description": "The phone number of the sender" + }, + "blocked": { + "type": "boolean", + "description": "Whether the sender is blocked" + }, + "identifier": { + "type": "string", + "description": "The identifier of the sender" + }, + "thumbnail": { + "type": "string", + "description": "Avatar URL of the contact" + }, + "custom_attributes": { + "type": "object", + "description": "The custom attributes of the sender" + }, + "last_activity_at": { + "type": "number", + "description": "The last activity at of the sender" + }, + "created_at": { + "type": "number", + "description": "The created at of the sender" + } + } + }, + "channel": { + "type": "string", + "description": "Channel Type" + }, + "assignee": { + "$ref": "#/components/schemas/user" + }, + "hmac_verified": { + "type": "boolean", + "description": "Whether the hmac is verified" + } + } + } + } + }, + { + "type": "object", + "properties": { + "display_id": { + "type": "number" + } + } + } + ] + } + }, + "contact_labels": { + "type": "object", + "properties": { + "payload": { + "type": "array", + "description": "Array of labels", + "items": { + "type": "string" + } + } + } + }, + "conversation_list": { + "type": "object", + "properties": { + "data": { + "type": "object", + "properties": { + "meta": { + "type": "object", + "properties": { + "mine_count": { + "type": "number" + }, + "unassigned_count": { + "type": "number" + }, + "assigned_count": { + "type": "number" + }, + "all_count": { + "type": "number" + } + } + }, + "payload": { + "type": "array", + "description": "array of conversations", + "items": { + "allOf": [ + { + "$ref": "#/components/schemas/generic_id" + }, + { + "$ref": "#/components/schemas/conversation" + }, + { + "type": "object", + "properties": { + "meta": { + "type": "object", + "properties": { + "sender": { + "type": "object", + "properties": { + "additional_attributes": { + "type": "object", + "description": "The additional attributes of the sender" + }, + "availability_status": { + "type": "string", + "description": "The availability status of the sender" + }, + "email": { + "type": "string", + "description": "The email of the sender" + }, + "id": { + "type": "number", + "description": "ID fo the sender" + }, + "name": { + "type": "string", + "description": "The name of the sender" + }, + "phone_number": { + "type": "string", + "description": "The phone number of the sender" + }, + "blocked": { + "type": "boolean", + "description": "Whether the sender is blocked" + }, + "identifier": { + "type": "string", + "description": "The identifier of the sender" + }, + "thumbnail": { + "type": "string", + "description": "Avatar URL of the contact" + }, + "custom_attributes": { + "type": "object", + "description": "The custom attributes of the sender" + }, + "last_activity_at": { + "type": "number", + "description": "The last activity at of the sender" + }, + "created_at": { + "type": "number", + "description": "The created at of the sender" + } + } + }, + "channel": { + "type": "string", + "description": "Channel Type" + }, + "assignee": { + "$ref": "#/components/schemas/user" + }, + "hmac_verified": { + "type": "boolean", + "description": "Whether the hmac is verified" + } + } + } + } + } + ] + } + } + } + } + } + }, + "conversation_show": { + "type": "object", + "allOf": [ + { + "$ref": "#/components/schemas/conversation" + }, + { + "type": "object", + "properties": { + "meta": { + "type": "object", + "properties": { + "sender": { + "type": "object", + "properties": { + "additional_attributes": { + "type": "object", + "description": "The additional attributes of the sender" + }, + "availability_status": { + "type": "string", + "description": "The availability status of the sender" + }, + "email": { + "type": "string", + "description": "The email of the sender" + }, + "id": { + "type": "number", + "description": "ID fo the sender" + }, + "name": { + "type": "string", + "description": "The name of the sender" + }, + "phone_number": { + "type": "string", + "description": "The phone number of the sender" + }, + "blocked": { + "type": "boolean", + "description": "Whether the sender is blocked" + }, + "identifier": { + "type": "string", + "description": "The identifier of the sender" + }, + "thumbnail": { + "type": "string", + "description": "Avatar URL of the contact" + }, + "custom_attributes": { + "type": "object", + "description": "The custom attributes of the sender" + }, + "last_activity_at": { + "type": "number", + "description": "The last activity at of the sender" + }, + "created_at": { + "type": "number", + "description": "The created at of the sender" + } + } + }, + "channel": { + "type": "string", + "description": "Channel Type" + }, + "assignee": { + "$ref": "#/components/schemas/user" + }, + "hmac_verified": { + "type": "boolean", + "description": "Whether the hmac is verified" + } + } + } + } + } + ] + }, + "conversation_status_toggle": { + "type": "object", + "properties": { + "meta": { + "type": "object" + }, + "payload": { + "type": "object", + "properties": { + "success": { + "type": "boolean" + }, + "current_status": { + "type": "string", + "enum": [ + "open", + "resolved" + ] + }, + "conversation_id": { + "type": "number" + } + } + } + } + }, + "conversation_labels": { + "type": "object", + "properties": { + "payload": { + "type": "array", + "description": "Array of labels", + "items": { + "type": "string" + } + } + } + }, + "account_summary": { + "type": "object", + "properties": { + "avg_first_response_time": { + "type": "string" + }, + "avg_resolution_time": { + "type": "string" + }, + "conversations_count": { + "type": "number" + }, + "incoming_messages_count": { + "type": "number" + }, + "outgoing_messages_count": { + "type": "number" + }, + "resolutions_count": { + "type": "number" + }, + "previous": { + "type": "object", + "properties": { + "avg_first_response_time": { + "type": "string" + }, + "avg_resolution_time": { + "type": "string" + }, + "conversations_count": { + "type": "number" + }, + "incoming_messages_count": { + "type": "number" + }, + "outgoing_messages_count": { + "type": "number" + }, + "resolutions_count": { + "type": "number" + } + } + } + } + }, + "agent_conversation_metrics": { + "type": "object", + "properties": { + "id": { + "type": "number" + }, + "name": { + "type": "string" + }, + "email": { + "type": "string" + }, + "thumbnail": { + "type": "string" + }, + "availability": { + "type": "string" + }, + "metric": { + "type": "object", + "properties": { + "open": { + "type": "number" + }, + "unattended": { + "type": "number" + } + } + } + } + }, + "contact_detail": { + "type": "object", + "properties": { + "additional_attributes": { + "type": "object", + "description": "The object containing additional attributes related to the contact", + "properties": { + "city": { + "type": "string", + "description": "City of the contact" + }, + "country": { + "type": "string", + "description": "Country of the contact" + }, + "country_code": { + "type": "string", + "description": "Country code of the contact" + }, + "created_at_ip": { + "type": "string", + "description": "IP address when the contact was created" + } + } + }, + "custom_attributes": { + "type": "object", + "description": "The custom attributes of the contact" + }, + "email": { + "type": "string", + "description": "The email address of the contact" + }, + "id": { + "type": "integer", + "description": "The ID of the contact" + }, + "identifier": { + "type": "string", + "description": "The identifier of the contact", + "nullable": true + }, + "name": { + "type": "string", + "description": "The name of the contact" + }, + "phone_number": { + "type": "string", + "description": "The phone number of the contact", + "nullable": true + }, + "thumbnail": { + "type": "string", + "description": "The thumbnail of the contact" + }, + "blocked": { + "type": "boolean", + "description": "Whether the contact is blocked" + }, + "type": { + "type": "string", + "description": "The type of entity", + "enum": [ + "contact" + ] + } + } + }, + "message_detailed": { + "type": "object", + "properties": { + "id": { + "type": "number", + "description": "The ID of the message" + }, + "content": { + "type": "string", + "description": "The text content of the message" + }, + "inbox_id": { + "type": "number", + "description": "The ID of the inbox" + }, + "conversation_id": { + "type": "number", + "description": "The ID of the conversation" + }, + "message_type": { + "type": "integer", + "enum": [ + 0, + 1, + 2, + 3 + ], + "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" + ], + "description": "The type of the message content" + }, + "status": { + "type": "string", + "enum": [ + "sent", + "delivered", + "read", + "failed" + ], + "description": "The status of the message" + }, + "content_attributes": { + "type": "object", + "description": "The content attributes for each content_type", + "properties": { + "in_reply_to": { + "type": "string", + "description": "ID of the message this is replying to", + "nullable": true + } + } + }, + "created_at": { + "type": "integer", + "description": "The timestamp when message was created" + }, + "private": { + "type": "boolean", + "description": "The flag which shows whether the message is private or not" + }, + "source_id": { + "type": "string", + "description": "The source ID of the message", + "nullable": true + }, + "sender": { + "$ref": "#/components/schemas/contact_detail" + } + } + }, + "conversation_meta": { + "type": "object", + "properties": { + "labels": { + "type": "array", + "items": { + "type": "string" + }, + "description": "Labels associated with the conversation" + }, + "additional_attributes": { + "type": "object", + "properties": { + "browser": { + "type": "object", + "properties": { + "device_name": { + "type": "string", + "description": "Name of the device" + }, + "browser_name": { + "type": "string", + "description": "Name of the browser" + }, + "platform_name": { + "type": "string", + "description": "Name of the platform" + }, + "browser_version": { + "type": "string", + "description": "Version of the browser" + }, + "platform_version": { + "type": "string", + "description": "Version of the platform" + } + } + }, + "referer": { + "type": "string", + "description": "Referrer URL" + }, + "initiated_at": { + "type": "object", + "properties": { + "timestamp": { + "type": "string", + "description": "Timestamp when the conversation was initiated" + } + } + }, + "browser_language": { + "type": "string", + "description": "Browser language setting" + }, + "conversation_language": { + "type": "string", + "description": "Conversation language" + } + }, + "description": "Additional attributes of the conversation" + }, + "contact": { + "$ref": "#/components/schemas/contact_detail" + }, + "agent_last_seen_at": { + "type": "string", + "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 + } + } + }, + "conversation_messages": { + "type": "object", + "properties": { + "meta": { + "$ref": "#/components/schemas/conversation_meta" + }, + "payload": { + "type": "array", + "items": { + "$ref": "#/components/schemas/message_detailed" + }, + "description": "List of messages in the conversation" + } + } + }, + "contact_meta": { + "type": "object", + "properties": { + "count": { + "type": "integer", + "description": "Total number of contacts" + }, + "current_page": { + "type": "string", + "description": "Current page number" + } + } + }, + "contact_inbox": { + "type": "object", + "properties": { + "source_id": { + "type": "string", + "description": "Source identifier for the contact inbox" + }, + "inbox": { + "type": "object", + "properties": { + "id": { + "type": "integer", + "description": "ID of the inbox" + }, + "avatar_url": { + "type": "string", + "description": "URL for the inbox avatar" + }, + "channel_id": { + "type": "integer", + "description": "ID of the channel" + }, + "name": { + "type": "string", + "description": "Name of the inbox" + }, + "channel_type": { + "type": "string", + "description": "Type of channel" + }, + "provider": { + "type": "string", + "description": "Provider of the inbox", + "nullable": true + } + } + } + } + }, + "contact_list_item": { + "type": "object", + "properties": { + "additional_attributes": { + "type": "object", + "description": "The object containing additional attributes related to the contact", + "properties": { + "city": { + "type": "string", + "description": "City of the contact" + }, + "country": { + "type": "string", + "description": "Country of the contact" + }, + "country_code": { + "type": "string", + "description": "Country code of the contact" + }, + "created_at_ip": { + "type": "string", + "description": "IP address when the contact was created" + } + } + }, + "availability_status": { + "type": "string", + "description": "Availability status of the contact", + "enum": [ + "online", + "offline" + ] + }, + "email": { + "type": "string", + "description": "The email address of the contact", + "nullable": true + }, + "id": { + "type": "integer", + "description": "The ID of the contact" + }, + "name": { + "type": "string", + "description": "The name of the contact" + }, + "phone_number": { + "type": "string", + "description": "The phone number of the contact", + "nullable": true + }, + "blocked": { + "type": "boolean", + "description": "Whether the contact is blocked" + }, + "identifier": { + "type": "string", + "description": "The identifier of the contact", + "nullable": true + }, + "thumbnail": { + "type": "string", + "description": "The thumbnail of the contact" + }, + "custom_attributes": { + "type": "object", + "description": "The custom attributes of the contact" + }, + "last_activity_at": { + "type": "integer", + "description": "Timestamp of last activity", + "nullable": true + }, + "created_at": { + "type": "integer", + "description": "Timestamp when contact was created" + }, + "contact_inboxes": { + "type": "array", + "description": "List of inboxes associated with this contact", + "items": { + "$ref": "#/components/schemas/contact_inbox" + } + } + } + }, + "contacts_list_response": { + "type": "object", + "properties": { + "meta": { + "$ref": "#/components/schemas/contact_meta" + }, + "payload": { + "type": "array", + "items": { + "$ref": "#/components/schemas/contact_list_item" + }, + "description": "List of contacts" + } + } + }, + "contact_show_response": { + "type": "object", + "properties": { + "payload": { + "$ref": "#/components/schemas/contact_list_item" + } + } + }, + "contact_conversation_message": { + "type": "object", + "properties": { + "id": { + "type": "integer", + "description": "ID of the message" + }, + "content": { + "type": "string", + "description": "Content of the message" + }, + "account_id": { + "type": "integer", + "description": "ID of the account" + }, + "inbox_id": { + "type": "integer", + "description": "ID of the inbox" + }, + "conversation_id": { + "type": "integer", + "description": "ID of the conversation" + }, + "message_type": { + "type": "integer", + "description": "Type of the message" + }, + "created_at": { + "type": "integer", + "description": "Timestamp when message was created" + }, + "updated_at": { + "type": "string", + "description": "Formatted datetime when message was updated" + }, + "private": { + "type": "boolean", + "description": "Whether the message is private" + }, + "status": { + "type": "string", + "description": "Status of the message" + }, + "source_id": { + "type": "string", + "description": "Source ID of the message", + "nullable": true + }, + "content_type": { + "type": "string", + "description": "Type of the content" + }, + "content_attributes": { + "type": "object", + "description": "Attributes of the content" + }, + "sender_type": { + "type": "string", + "description": "Type of the sender", + "nullable": true + }, + "sender_id": { + "type": "integer", + "description": "ID of the sender", + "nullable": true + }, + "external_source_ids": { + "type": "object", + "description": "External source IDs" + }, + "additional_attributes": { + "type": "object", + "description": "Additional attributes of the message" + }, + "processed_message_content": { + "type": "string", + "description": "Processed message content", + "nullable": true + }, + "sentiment": { + "type": "object", + "description": "Sentiment analysis of the message" + }, + "conversation": { + "type": "object", + "description": "Conversation details", + "properties": { + "assignee_id": { + "type": "integer", + "description": "ID of the assignee", + "nullable": true + }, + "unread_count": { + "type": "integer", + "description": "Count of unread messages" + }, + "last_activity_at": { + "type": "integer", + "description": "Timestamp of last activity" + }, + "contact_inbox": { + "type": "object", + "description": "Contact inbox details", + "properties": { + "source_id": { + "type": "string", + "description": "Source ID of the contact inbox" + } + } + } + } + }, + "sender": { + "type": "object", + "description": "Details of the sender", + "properties": { + "id": { + "type": "integer", + "description": "ID of the sender" + }, + "name": { + "type": "string", + "description": "Name of the sender" + }, + "available_name": { + "type": "string", + "description": "Available name of the sender" + }, + "avatar_url": { + "type": "string", + "description": "URL of the sender's avatar" + }, + "type": { + "type": "string", + "description": "Type of the sender" + }, + "availability_status": { + "type": "string", + "description": "Availability status of the sender" + }, + "thumbnail": { + "type": "string", + "description": "Thumbnail URL of the sender" + } + } + } + } + }, + "contact_conversations_response": { + "type": "object", + "properties": { + "payload": { + "type": "array", + "items": { + "allOf": [ + { + "$ref": "#/components/schemas/conversation" + }, + { + "type": "object", + "properties": { + "meta": { + "type": "object", + "properties": { + "sender": { + "type": "object", + "properties": { + "additional_attributes": { + "type": "object", + "description": "The additional attributes of the sender" + }, + "availability_status": { + "type": "string", + "description": "The availability status of the sender" + }, + "email": { + "type": "string", + "description": "The email of the sender" + }, + "id": { + "type": "number", + "description": "ID fo the sender" + }, + "name": { + "type": "string", + "description": "The name of the sender" + }, + "phone_number": { + "type": "string", + "description": "The phone number of the sender" + }, + "blocked": { + "type": "boolean", + "description": "Whether the sender is blocked" + }, + "identifier": { + "type": "string", + "description": "The identifier of the sender" + }, + "thumbnail": { + "type": "string", + "description": "Avatar URL of the contact" + }, + "custom_attributes": { + "type": "object", + "description": "The custom attributes of the sender" + }, + "last_activity_at": { + "type": "number", + "description": "The last activity at of the sender" + }, + "created_at": { + "type": "number", + "description": "The created at of the sender" + } + } + }, + "channel": { + "type": "string", + "description": "Channel Type" + }, + "assignee": { + "$ref": "#/components/schemas/user" + }, + "hmac_verified": { + "type": "boolean", + "description": "Whether the hmac is verified" + } + } + } + } + } + ] + }, + "description": "List of conversations for the contact" + } + } + }, + "contactable_inboxes_response": { + "type": "object", + "properties": { + "payload": { + "type": "array", + "items": { + "$ref": "#/components/schemas/contact_inbox" + }, + "description": "List of contactable inboxes for the contact" + } + } + } + }, + "parameters": { + "account_id": { + "in": "path", + "name": "account_id", + "schema": { + "type": "integer" + }, + "required": true, + "description": "The numeric ID of the account" + }, + "agent_bot_id": { + "in": "path", + "name": "id", + "schema": { + "type": "integer" + }, + "required": true, + "description": "The ID of the agentbot to be updated" + }, + "team_id": { + "in": "path", + "name": "team_id", + "schema": { + "type": "integer" + }, + "required": true, + "description": "The ID of the team to be updated" + }, + "inbox_id": { + "in": "path", + "name": "inbox_id", + "schema": { + "type": "integer" + }, + "required": true, + "description": "The ID of the Inbox" + }, + "hook_id": { + "in": "path", + "name": "hook_id", + "schema": { + "type": "integer" + }, + "required": true, + "description": "The numeric ID of the integration hook" + }, + "source_id": { + "in": "path", + "name": "source_id", + "required": true, + "schema": { + "type": "string" + }, + "description": "Id of the session for which the conversation is created.\n\n\n\n Source Ids can be obtained through contactable inboxes API or via generated.Website: Chatwoot generated string which can be obtained from webhook events. Phone Channels(Twilio): Phone number in e164 format Email Channels: Contact Email address API Channel: Any Random String" + }, + "contact_sort_param": { + "in": "query", + "name": "sort", + "schema": { + "type": "string", + "enum": [ + "name", + "email", + "phone_number", + "last_activity_at", + "-name", + "-email", + "-phone_number", + "-last_activity_at" + ] + }, + "required": false, + "description": "The attribute by which list should be sorted" + }, + "conversation_id": { + "in": "path", + "name": "conversation_id", + "schema": { + "type": "integer" + }, + "required": true, + "description": "The numeric ID of the conversation" + }, + "conversation_uuid": { + "in": "path", + "name": "conversation_uuid", + "schema": { + "type": "integer" + }, + "required": true, + "description": "The uuid of the conversation" + }, + "custom_filter_id": { + "in": "path", + "name": "custom_filter_id", + "schema": { + "type": "integer" + }, + "required": true, + "description": "The numeric ID of the custom filter" + }, + "webhook_id": { + "in": "path", + "name": "webhook_id", + "schema": { + "type": "integer" + }, + "required": true, + "description": "The numeric ID of the webhook" + }, + "message_id": { + "in": "path", + "name": "message_id", + "schema": { + "type": "integer" + }, + "required": true, + "description": "The numeric ID of the message" + }, + "page": { + "in": "query", + "name": "page", + "schema": { + "type": "integer", + "default": 1 + }, + "required": false, + "description": "The page parameter" + }, + "platform_user_id": { + "in": "path", + "name": "id", + "schema": { + "type": "integer" + }, + "required": true, + "description": "The numeric ID of the user on the platform" + }, + "report_type": { + "in": "query", + "name": "type", + "schema": { + "type": "string", + "enum": [ + "account", + "agent", + "inbox", + "label", + "team" + ] + }, + "required": true, + "description": "Type of report" + }, + "report_metric": { + "in": "query", + "name": "metric", + "schema": { + "type": "string", + "enum": [ + "conversations_count", + "incoming_messages_count", + "outgoing_messages_count", + "avg_first_response_time", + "avg_resolution_time", + "resolutions_count" + ] + }, + "required": true, + "description": "The type of metric" + }, + "public_inbox_identifier": { + "in": "path", + "name": "inbox_identifier", + "schema": { + "type": "string" + }, + "required": true, + "description": "The identifier obtained from API inbox channel" + }, + "public_contact_identifier": { + "in": "path", + "name": "contact_identifier", + "schema": { + "type": "string" + }, + "required": true, + "description": "The source id of contact obtained on contact create" + }, + "portal_id": { + "in": "path", + "name": "id", + "schema": { + "type": "string" + }, + "required": true, + "description": "The slug identifier of the portal" + } + }, + "securitySchemes": { + "userApiKey": { + "type": "apiKey", + "in": "header", + "name": "api_access_token", + "description": "This token can be obtained by visiting the profile page or via rails console. Provides access to endpoints based on the user permissions levels. This token can be saved by an external system when user is created via API, to perform activities on behalf of the user." + }, + "agentBotApiKey": { + "type": "apiKey", + "in": "header", + "name": "api_access_token", + "description": "This token should be provided by system admin or obtained via rails console. This token can be used to build bot integrations and can only access limited apis." + }, + "platformAppApiKey": { + "type": "apiKey", + "in": "header", + "name": "api_access_token", + "description": "This token can be obtained by the system admin after creating a platformApp. This token should be used to provision agent bots, accounts, users and their roles." + } + } + }, + "tags": [ + { + "name": "Account AgentBots", + "description": "Account-specific Agent Bots" + }, + { + "name": "Agents", + "description": "Agent management APIs" + }, + { + "name": "Canned Responses", + "description": "Pre-defined responses for common queries" + }, + { + "name": "Contacts", + "description": "Contact management APIs" + }, + { + "name": "Contact Labels", + "description": "Manage contact labels" + }, + { + "name": "Conversation Assignments", + "description": "Manage conversation assignments" + }, + { + "name": "Conversation Labels", + "description": "Manage conversation labels" + }, + { + "name": "Conversations", + "description": "Conversation management APIs" + }, + { + "name": "Custom Attributes", + "description": "Custom fields for contacts and conversations" + }, + { + "name": "Custom Filters", + "description": "Saved filters for conversations" + }, + { + "name": "Inboxes", + "description": "Communication channels setup" + }, + { + "name": "Integrations", + "description": "Third-party integrations" + }, + { + "name": "Messages", + "description": "Message management APIs" + }, + { + "name": "Profile", + "description": "User profile APIs" + }, + { + "name": "Reports", + "description": "Analytics and reporting APIs" + }, + { + "name": "Teams", + "description": "Team management APIs" + }, + { + "name": "Webhooks", + "description": "Event notification webhooks" + }, + { + "name": "Automation Rule", + "description": "Workflow automation rules" + }, + { + "name": "Help Center", + "description": "Knowledge base management" + } + ], + "x-tagGroups": [ + { + "name": "Platform", + "tags": [ + "Accounts", + "Account Users", + "AgentBots", + "Users" + ] + }, + { + "name": "Application", + "tags": [ + "Account AgentBots", + "Agents", + "Canned Responses", + "Contacts", + "Contact Labels", + "Conversation Assignments", + "Conversation Labels", + "Conversations", + "Custom Attributes", + "Custom Filters", + "Inboxes", + "Integrations", + "Messages", + "Profile", + "Reports", + "Teams", + "Webhooks", + "Automation Rule", + "Help Center" + ] + }, + { + "name": "Client", + "tags": [ + "Contacts API", + "Conversations API", + "Messages API" + ] + }, + { + "name": "Others", + "tags": [ + "CSAT Survey Page" + ] + } + ] +} \ No newline at end of file diff --git a/swagger/tag_groups/client.yml b/swagger/tag_groups/client.yml new file mode 100644 index 000000000..fdd177b97 --- /dev/null +++ b/swagger/tag_groups/client.yml @@ -0,0 +1,33 @@ +openapi: '3.0.4' +info: + title: Chatwoot - Client API + description: Client API endpoints for Chatwoot + version: 1.1.0 + termsOfService: https://www.chatwoot.com/terms-of-service/ + contact: + email: hello@chatwoot.com + license: + name: MIT License + url: https://opensource.org/licenses/MIT +servers: + - url: https://app.chatwoot.com/ +tags: + - name: Contacts API + description: APIs for managing contacts from client applications + - name: Conversations API + description: APIs for managing conversations from client applications + - name: Messages API + description: APIs for managing messages from client applications +paths: + $ref: ../paths/index.yml +components: + schemas: + $ref: ../definitions/index.yml + parameters: + $ref: ../parameters/index.yml + securitySchemes: + userApiKey: + type: apiKey + in: header + name: api_access_token + description: This token can be obtained by visiting the profile page or via rails console. Provides access to endpoints based on the user permissions levels. \ No newline at end of file diff --git a/swagger/tag_groups/client_swagger.json b/swagger/tag_groups/client_swagger.json new file mode 100644 index 000000000..6d471da43 --- /dev/null +++ b/swagger/tag_groups/client_swagger.json @@ -0,0 +1,4963 @@ +{ + "openapi": "3.0.4", + "info": { + "title": "Chatwoot", + "description": "This is the API documentation for Chatwoot server.", + "version": "1.1.0", + "termsOfService": "https://www.chatwoot.com/terms-of-service/", + "contact": { + "email": "hello@chatwoot.com" + }, + "license": { + "name": "MIT License", + "url": "https://opensource.org/licenses/MIT" + } + }, + "servers": [ + { + "url": "https://app.chatwoot.com/" + } + ], + "paths": { + "/public/api/v1/inboxes/{inbox_identifier}/contacts": { + "parameters": [ + { + "$ref": "#/components/parameters/public_inbox_identifier" + } + ], + "post": { + "tags": [ + "Contacts API" + ], + "operationId": "create-a-contact", + "summary": "Create a contact", + "description": "Create a contact", + "security": [], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/public_contact_create_update_payload" + } + } + } + }, + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/public_contact" + } + } + } + }, + "401": { + "description": "Unauthorized", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/bad_request_error" + } + } + } + } + } + } + }, + "/public/api/v1/inboxes/{inbox_identifier}/contacts/{contact_identifier}": { + "parameters": [ + { + "$ref": "#/components/parameters/public_inbox_identifier" + }, + { + "$ref": "#/components/parameters/public_contact_identifier" + } + ], + "get": { + "tags": [ + "Contacts API" + ], + "operationId": "get-details-of-a-contact", + "summary": "Get a contact", + "description": "Get the details of a contact", + "security": [], + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/public_contact" + } + } + } + }, + "401": { + "description": "Unauthorized", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/bad_request_error" + } + } + } + }, + "404": { + "description": "The given contact does not exist", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/bad_request_error" + } + } + } + } + } + }, + "patch": { + "tags": [ + "Contacts API" + ], + "operationId": "update-a-contact", + "summary": "Update a contact", + "description": "Update a contact's attributes", + "security": [], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/public_contact_create_update_payload" + } + } + } + }, + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/public_contact" + } + } + } + }, + "401": { + "description": "Unauthorized", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/bad_request_error" + } + } + } + } + } + } + }, + "/public/api/v1/inboxes/{inbox_identifier}/contacts/{contact_identifier}/conversations": { + "parameters": [ + { + "$ref": "#/components/parameters/public_inbox_identifier" + }, + { + "$ref": "#/components/parameters/public_contact_identifier" + } + ], + "post": { + "tags": [ + "Conversations API" + ], + "operationId": "create-a-conversation", + "summary": "Create a conversation", + "description": "Create a conversation", + "security": [], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/public_conversation_create_payload" + } + } + } + }, + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/public_conversation" + } + } + } + }, + "401": { + "description": "Unauthorized", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/bad_request_error" + } + } + } + } + } + }, + "get": { + "tags": [ + "Conversations API" + ], + "operationId": "list-all-contact-conversations", + "summary": "List all conversations", + "description": "List all conversations for the contact", + "security": [], + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "type": "array", + "description": "Array of conversations", + "items": { + "$ref": "#/components/schemas/public_conversation" + } + } + } + } + }, + "401": { + "description": "Unauthorized", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/bad_request_error" + } + } + } + } + } + } + }, + "/public/api/v1/inboxes/{inbox_identifier}/contacts/{contact_identifier}/conversations/{conversation_id}": { + "parameters": [ + { + "$ref": "#/components/parameters/public_inbox_identifier" + }, + { + "$ref": "#/components/parameters/public_contact_identifier" + }, + { + "$ref": "#/components/parameters/conversation_id" + } + ], + "get": { + "tags": [ + "Conversations API" + ], + "operationId": "get-single-conversation", + "summary": "Get a single conversation", + "description": "Retrieves the details of a specific conversation", + "security": [], + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/public_conversation" + } + } + } + }, + "401": { + "description": "Unauthorized", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/bad_request_error" + } + } + } + }, + "404": { + "description": "Conversation not found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/bad_request_error" + } + } + } + } + } + } + }, + "/public/api/v1/inboxes/{inbox_identifier}/contacts/{contact_identifier}/conversations/{conversation_id}/toggle_status": { + "parameters": [ + { + "$ref": "#/components/parameters/public_inbox_identifier" + }, + { + "$ref": "#/components/parameters/public_contact_identifier" + }, + { + "$ref": "#/components/parameters/conversation_id" + } + ], + "post": { + "tags": [ + "Conversations API" + ], + "operationId": "resolve-conversation", + "summary": "Resolve a conversation", + "description": "Marks a conversation as resolved", + "security": [], + "responses": { + "200": { + "description": "Conversation resolved successfully", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/public_conversation" + } + } + } + }, + "401": { + "description": "Unauthorized", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/bad_request_error" + } + } + } + }, + "404": { + "description": "Conversation not found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/bad_request_error" + } + } + } + } + } + } + }, + "/public/api/v1/inboxes/{inbox_identifier}/contacts/{contact_identifier}/conversations/{conversation_id}/toggle_typing": { + "parameters": [ + { + "$ref": "#/components/parameters/public_inbox_identifier" + }, + { + "$ref": "#/components/parameters/public_contact_identifier" + }, + { + "$ref": "#/components/parameters/conversation_id" + } + ], + "post": { + "tags": [ + "Conversations API" + ], + "operationId": "toggle-typing-status", + "summary": "Toggle typing status", + "description": "Toggles the typing status in a conversation", + "security": [], + "parameters": [ + { + "name": "typing_status", + "in": "query", + "required": true, + "schema": { + "type": "string" + }, + "description": "Typing status, either 'on' or 'off'" + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "typing_status": { + "type": "string", + "enum": [ + "on", + "off" + ], + "description": "The typing status to set", + "example": "on" + } + } + } + } + } + }, + "responses": { + "200": { + "description": "Typing status toggled successfully" + }, + "401": { + "description": "Unauthorized", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/bad_request_error" + } + } + } + }, + "404": { + "description": "Conversation not found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/bad_request_error" + } + } + } + } + } + } + }, + "/public/api/v1/inboxes/{inbox_identifier}/contacts/{contact_identifier}/conversations/{conversation_id}/update_last_seen": { + "parameters": [ + { + "$ref": "#/components/parameters/public_inbox_identifier" + }, + { + "$ref": "#/components/parameters/public_contact_identifier" + }, + { + "$ref": "#/components/parameters/conversation_id" + } + ], + "post": { + "tags": [ + "Conversations API" + ], + "operationId": "update-last-seen", + "summary": "Update last seen", + "description": "Updates the last seen time of the contact in a conversation", + "security": [], + "responses": { + "200": { + "description": "Last seen updated successfully" + }, + "401": { + "description": "Unauthorized", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/bad_request_error" + } + } + } + }, + "404": { + "description": "Conversation not found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/bad_request_error" + } + } + } + } + } + } + }, + "/public/api/v1/inboxes/{inbox_identifier}/contacts/{contact_identifier}/conversations/{conversation_id}/messages": { + "parameters": [ + { + "$ref": "#/components/parameters/public_inbox_identifier" + }, + { + "$ref": "#/components/parameters/public_contact_identifier" + }, + { + "$ref": "#/components/parameters/conversation_id" + } + ], + "post": { + "tags": [ + "Messages API" + ], + "operationId": "create-a-message", + "summary": "Create a message", + "description": "Create a message", + "security": [], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/public_message_create_payload" + } + } + } + }, + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/public_message" + } + } + } + }, + "401": { + "description": "Unauthorized", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/bad_request_error" + } + } + } + } + } + }, + "get": { + "tags": [ + "Messages API" + ], + "operationId": "list-all-converation-messages", + "summary": "List all messages", + "description": "List all messages in the conversation", + "security": [], + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "type": "array", + "description": "Array of messages", + "items": { + "$ref": "#/components/schemas/public_message" + } + } + } + } + }, + "401": { + "description": "Unauthorized", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/bad_request_error" + } + } + } + } + } + } + }, + "/public/api/v1/inboxes/{inbox_identifier}/contacts/{contact_identifier}/conversations/{conversation_id}/messages/{message_id}": { + "parameters": [ + { + "$ref": "#/components/parameters/public_inbox_identifier" + }, + { + "$ref": "#/components/parameters/public_contact_identifier" + }, + { + "$ref": "#/components/parameters/conversation_id" + }, + { + "$ref": "#/components/parameters/message_id" + } + ], + "patch": { + "tags": [ + "Messages API" + ], + "operationId": "update-a-message", + "summary": "Update a message", + "description": "Update a message", + "security": [], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/public_message_update_payload" + } + } + } + }, + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/public_message" + } + } + } + }, + "401": { + "description": "Unauthorized", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/bad_request_error" + } + } + } + } + } + } + } + }, + "components": { + "schemas": { + "bad_request_error": { + "title": "data", + "type": "object", + "properties": { + "description": { + "type": "string" + }, + "errors": { + "type": "array", + "items": { + "$ref": "#/components/schemas/request_error" + } + } + } + }, + "request_error": { + "type": "object", + "properties": { + "field": { + "type": "string" + }, + "message": { + "type": "string" + }, + "code": { + "type": "string" + } + } + }, + "generic_id": { + "type": "object", + "properties": { + "id": { + "type": "number" + } + } + }, + "canned_response": { + "type": "object", + "properties": { + "id": { + "type": "integer", + "description": "ID of the canned response" + }, + "account_id": { + "type": "integer", + "description": "Account Id" + }, + "short_code": { + "type": "string", + "description": "Short Code for quick access of the canned response" + }, + "content": { + "type": "string", + "description": "Message content for canned response" + }, + "created_at": { + "type": "string", + "description": "The date and time when the canned response was created" + }, + "updated_at": { + "type": "string", + "description": "The date and time when the canned response was updated" + } + } + }, + "custom_attribute": { + "type": "object", + "properties": { + "id": { + "type": "integer", + "description": "Identifier" + }, + "attribute_display_name": { + "type": "string", + "description": "Attribute display name" + }, + "attribute_display_type": { + "type": "string", + "description": "Attribute display type (text, number, currency, percent, link, date, list, checkbox)" + }, + "attribute_description": { + "type": "string", + "description": "Attribute description" + }, + "attribute_key": { + "type": "string", + "description": "Attribute unique key value" + }, + "regex_pattern": { + "type": "string", + "description": "Regex pattern" + }, + "regex_cue": { + "type": "string", + "description": "Regex cue" + }, + "attribute_values": { + "type": "string", + "description": "Attribute values" + }, + "attribute_model": { + "type": "string", + "description": "Attribute type(conversation_attribute/contact_attribute)" + }, + "default_value": { + "type": "string", + "description": "Attribute default value" + }, + "created_at": { + "type": "string", + "description": "The date and time when the custom attribute was created" + }, + "updated_at": { + "type": "string", + "description": "The date and time when the custom attribute was updated" + } + } + }, + "automation_rule": { + "type": "object", + "properties": { + "payload": { + "description": "Response payload that contains automation rule(s)", + "oneOf": [ + { + "type": "array", + "description": "Array of automation rules (for listing endpoint)", + "items": { + "$ref": "#/components/schemas/automation_rule_item" + } + }, + { + "type": "object", + "description": "Single automation rule (for show/create/update endpoints)", + "allOf": [ + { + "$ref": "#/components/schemas/automation_rule_item" + } + ] + } + ] + } + } + }, + "automation_rule_item": { + "type": "object", + "properties": { + "id": { + "type": "integer", + "description": "The ID of the automation rule" + }, + "account_id": { + "type": "integer", + "description": "Account Id" + }, + "name": { + "type": "string", + "description": "The name of the rule", + "example": "Add label on message create event" + }, + "description": { + "type": "string", + "description": "Description to give more context about the rule", + "example": "Add label support and sales on message create event if incoming message content contains text help" + }, + "event_name": { + "type": "string", + "description": "Automation Rule event, on which we call the actions(conversation_created, conversation_updated, message_created)", + "enum": [ + "conversation_created", + "conversation_updated", + "message_created" + ], + "example": "message_created" + }, + "conditions": { + "type": "array", + "description": "Array of conditions on which conversation/message filter would work", + "items": { + "type": "object", + "properties": { + "values": { + "type": "array", + "items": { + "type": "string" + } + }, + "attribute_key": { + "type": "string" + }, + "query_operator": { + "type": "string" + }, + "filter_operator": { + "type": "string" + } + }, + "example": { + "attribute_key": "content", + "filter_operator": "contains", + "values": [ + "help" + ], + "query_operator": "and" + } + } + }, + "actions": { + "type": "array", + "description": "Array of actions which we perform when condition matches", + "items": { + "type": "object", + "properties": { + "action_name": { + "type": "string" + }, + "action_params": { + "type": "array", + "items": { + "type": "string" + } + } + }, + "example": { + "action_name": "add_label", + "action_params": [ + "support", + "sales" + ] + } + } + }, + "created_on": { + "type": "integer", + "description": "The timestamp when the rule was created" + }, + "active": { + "type": "boolean", + "description": "Enable/disable automation rule" + } + } + }, + "portal": { + "type": "object", + "properties": { + "payload": { + "type": "array", + "items": { + "$ref": "#/components/schemas/portal_item" + } + } + } + }, + "portal_single": { + "type": "object", + "properties": { + "payload": { + "$ref": "#/components/schemas/portal_item" + } + } + }, + "portal_config": { + "type": "object", + "description": "Configuration settings for the portal", + "properties": { + "allowed_locales": { + "type": "array", + "description": "List of allowed locales for the portal", + "items": { + "type": "object", + "properties": { + "code": { + "type": "string", + "description": "The language code" + }, + "articles_count": { + "type": "integer", + "description": "Number of articles in this locale" + }, + "categories_count": { + "type": "integer", + "description": "Number of categories in this locale" + } + } + } + } + } + }, + "portal_logo": { + "type": "object", + "properties": { + "id": { + "type": "integer", + "description": "ID of the logo file" + }, + "portal_id": { + "type": "integer", + "description": "ID of the portal this logo belongs to" + }, + "file_type": { + "type": "string", + "description": "MIME type of the file" + }, + "account_id": { + "type": "integer", + "description": "ID of the account" + }, + "file_url": { + "type": "string", + "description": "URL to access the logo file" + }, + "blob_id": { + "type": "integer", + "description": "ID of the blob" + }, + "filename": { + "type": "string", + "description": "Name of the file" + } + } + }, + "portal_meta": { + "type": "object", + "properties": { + "all_articles_count": { + "type": "integer", + "description": "Total number of articles" + }, + "archived_articles_count": { + "type": "integer", + "nullable": true, + "description": "Number of archived articles" + }, + "published_count": { + "type": "integer", + "nullable": true, + "description": "Number of published articles" + }, + "draft_articles_count": { + "type": "integer", + "nullable": true, + "description": "Number of draft articles" + }, + "categories_count": { + "type": "integer", + "description": "Number of categories" + }, + "default_locale": { + "type": "string", + "description": "Default locale for the portal" + } + } + }, + "portal_item": { + "type": "object", + "properties": { + "id": { + "type": "integer", + "description": "The ID of the portal" + }, + "archived": { + "type": "boolean", + "description": "Whether the portal is archived" + }, + "color": { + "type": "string", + "description": "The color code for the portal" + }, + "config": { + "$ref": "#/components/schemas/portal_config" + }, + "custom_domain": { + "type": "string", + "description": "Custom domain for the portal" + }, + "header_text": { + "type": "string", + "description": "The header text for the portal" + }, + "homepage_link": { + "type": "string", + "description": "Homepage link for the portal" + }, + "name": { + "type": "string", + "description": "Name of the portal" + }, + "slug": { + "type": "string", + "description": "URL slug for the portal" + }, + "page_title": { + "type": "string", + "description": "Page title for the portal" + }, + "account_id": { + "type": "integer", + "description": "ID of the account the portal belongs to" + }, + "inbox": { + "$ref": "#/components/schemas/inbox" + }, + "logo": { + "$ref": "#/components/schemas/portal_logo" + }, + "meta": { + "$ref": "#/components/schemas/portal_meta" + } + } + }, + "category": { + "type": "object", + "properties": { + "id": { + "type": "integer" + }, + "description": { + "type": "string", + "description": "The text content." + }, + "locale": { + "type": "string" + }, + "name": { + "type": "string" + }, + "slug": { + "type": "string" + }, + "position": { + "type": "integer" + }, + "portal_id": { + "type": "integer" + }, + "account_id": { + "type": "integer" + }, + "associated_category_id": { + "type": "integer", + "description": "To associate similar categories to each other, e.g same category of product documentation in different languages" + }, + "parent_category_id": { + "type": "integer", + "description": "To define parent category, e.g product documentation has multiple level features in sales category or in engineering category." + } + } + }, + "article": { + "type": "object", + "properties": { + "id": { + "type": "integer" + }, + "content": { + "type": "string", + "description": "The text content." + }, + "meta": { + "type": "object" + }, + "position": { + "type": "integer" + }, + "status": { + "type": "integer", + "enum": [ + "draft", + "published", + "archived" + ] + }, + "title": { + "type": "string" + }, + "slug": { + "type": "string" + }, + "views": { + "type": "integer" + }, + "portal_id": { + "type": "integer" + }, + "account_id": { + "type": "integer" + }, + "author_id": { + "type": "integer" + }, + "category_id": { + "type": "integer" + }, + "folder_id": { + "type": "integer" + }, + "associated_article_id": { + "type": "integer", + "description": "To associate similar articles to each other, e.g to provide the link for the reference." + } + } + }, + "contact": { + "type": "object", + "properties": { + "payload": { + "type": "array", + "items": { + "type": "object", + "properties": { + "additional_attributes": { + "type": "object", + "description": "The object containing additional attributes related to the contact" + }, + "availability_status": { + "type": "string", + "description": "The availability status of the contact" + }, + "email": { + "type": "string", + "description": "The email address of the contact" + }, + "id": { + "type": "integer", + "description": "The ID of the contact" + }, + "name": { + "type": "string", + "description": "The name of the contact" + }, + "phone_number": { + "type": "string", + "description": "The phone number of the contact" + }, + "blocked": { + "type": "boolean", + "description": "Whether the contact is blocked" + }, + "identifier": { + "type": "string", + "description": "The identifier of the contact" + }, + "thumbnail": { + "type": "string", + "description": "The thumbnail of the contact" + }, + "custom_attributes": { + "type": "object", + "description": "The custom attributes of the contact", + "example": { + "attribute_key": "attribute_value", + "signed_up_at": "dd/mm/yyyy" + } + }, + "last_activity_at": { + "type": "integer", + "description": "The last activity at of the contact" + }, + "created_at": { + "type": "integer", + "description": "The created at of the contact" + }, + "contact_inboxes": { + "type": "array", + "items": { + "$ref": "#/components/schemas/contact_inboxes" + } + } + } + } + } + } + }, + "conversation": { + "type": "object", + "properties": { + "id": { + "type": "number", + "description": "ID of the conversation" + }, + "messages": { + "type": "array", + "items": { + "$ref": "#/components/schemas/message" + } + }, + "account_id": { + "type": "number", + "description": "Account Id" + }, + "uuid": { + "type": "string", + "description": "UUID of the conversation" + }, + "additional_attributes": { + "type": "object", + "description": "The object containing additional attributes related to the conversation" + }, + "agent_last_seen_at": { + "type": "number", + "description": "The last activity at of the agent" + }, + "assignee_last_seen_at": { + "type": "number", + "description": "The last activity at of the assignee" + }, + "can_reply": { + "type": "boolean", + "description": "Whether the conversation can be replied to" + }, + "contact_last_seen_at": { + "type": "number", + "description": "The last activity at of the contact" + }, + "custom_attributes": { + "type": "object", + "description": "The object to save custom attributes for conversation, accepts custom attributes key and value" + }, + "inbox_id": { + "type": "number", + "description": "ID of the inbox" + }, + "labels": { + "type": "array", + "items": { + "type": "string" + }, + "description": "The labels of the conversation" + }, + "muted": { + "type": "boolean", + "description": "Whether the conversation is muted" + }, + "snoozed_until": { + "type": "number", + "description": "The time at which the conversation will be unmuted" + }, + "status": { + "type": "string", + "enum": [ + "open", + "resolved", + "pending" + ], + "description": "The status of the conversation" + }, + "created_at": { + "type": "number", + "description": "The time at which conversation was created" + }, + "updated_at": { + "type": "number", + "description": "The time at which conversation was updated" + }, + "timestamp": { + "type": "string", + "description": "The time at which conversation was created" + }, + "first_reply_created_at": { + "type": "number", + "description": "The time at which the first reply was created" + }, + "unread_count": { + "type": "number", + "description": "The number of unread messages" + }, + "last_non_activity_message": { + "$ref": "#/components/schemas/message" + }, + "last_activity_at": { + "type": "number", + "description": "The last activity at of the conversation" + }, + "priority": { + "type": "string", + "description": "The priority of the conversation" + }, + "waiting_since": { + "type": "number", + "description": "The time at which the conversation was waiting" + }, + "sla_policy_id": { + "type": "number", + "description": "The ID of the SLA policy" + }, + "applied_sla": { + "type": "object", + "description": "The applied SLA" + }, + "sla_events": { + "type": "array", + "items": { + "type": "object", + "description": "SLA event objects" + } + } + } + }, + "message": { + "type": "object", + "properties": { + "id": { + "type": "number", + "description": "The ID of the message" + }, + "content": { + "type": "string", + "description": "The text content of the message" + }, + "account_id": { + "type": "number", + "description": "The ID of the account" + }, + "inbox_id": { + "type": "number", + "description": "The ID of the inbox" + }, + "conversation_id": { + "type": "number", + "description": "The ID of the conversation" + }, + "message_type": { + "type": "integer", + "enum": [ + 0, + 1, + 2 + ], + "description": "The type of the message" + }, + "created_at": { + "type": "integer", + "description": "The time at which message was created" + }, + "updated_at": { + "type": "integer", + "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" + ], + "description": "The status of the message" + }, + "source_id": { + "type": "string", + "description": "The source ID of the message" + }, + "content_type": { + "type": "string", + "enum": [ + "text", + "input_select", + "cards", + "form" + ], + "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" + ], + "description": "The type of the sender" + }, + "sender_id": { + "type": "number", + "description": "The ID of the sender" + }, + "external_source_ids": { + "type": "object", + "description": "The external source IDs of the message" + }, + "additional_attributes": { + "type": "object", + "description": "The additional attributes of the message" + }, + "processed_message_content": { + "type": "string", + "description": "The processed message content" + }, + "sentiment": { + "type": "object", + "description": "The sentiment of the message" + }, + "conversation": { + "type": "object", + "description": "The conversation object" + }, + "attachment": { + "type": "object", + "description": "The file object attached to the image" + }, + "sender": { + "type": "object", + "description": "User/Agent/AgentBot object" + } + } + }, + "user": { + "type": "object", + "properties": { + "id": { + "type": "number" + }, + "access_token": { + "type": "string" + }, + "account_id": { + "type": "number" + }, + "available_name": { + "type": "string" + }, + "avatar_url": { + "type": "string" + }, + "confirmed": { + "type": "boolean" + }, + "display_name": { + "type": "string", + "nullable": true + }, + "message_signature": { + "type": "string", + "nullable": true + }, + "email": { + "type": "string" + }, + "hmac_identifier": { + "type": "string" + }, + "inviter_id": { + "type": "number" + }, + "name": { + "type": "string" + }, + "provider": { + "type": "string" + }, + "pubsub_token": { + "type": "string" + }, + "role": { + "type": "string", + "enum": [ + "agent", + "administrator" + ] + }, + "ui_settings": { + "type": "object" + }, + "uid": { + "type": "string" + }, + "type": { + "type": "string", + "nullable": true + }, + "custom_attributes": { + "type": "object", + "description": "Available for users who are created through platform APIs and has custom attributes associated." + }, + "accounts": { + "type": "array", + "items": { + "type": "object", + "properties": { + "id": { + "type": "number" + }, + "name": { + "type": "string" + }, + "status": { + "type": "string" + }, + "active_at": { + "type": "string", + "format": "date-time" + }, + "role": { + "type": "string", + "enum": [ + "administrator", + "agent" + ] + }, + "permissions": { + "type": "array", + "items": { + "type": "string" + } + }, + "availability": { + "type": "string" + }, + "availability_status": { + "type": "string" + }, + "auto_offline": { + "type": "boolean" + }, + "custom_role_id": { + "type": "number", + "nullable": true + }, + "custom_role": { + "type": "object", + "nullable": true + } + } + } + } + } + }, + "agent": { + "type": "object", + "properties": { + "id": { + "type": "integer" + }, + "account_id": { + "type": "integer" + }, + "availability_status": { + "type": "string", + "enum": [ + "available", + "busy", + "offline" + ], + "description": "The availability status of the agent computed by Chatwoot." + }, + "auto_offline": { + "type": "boolean", + "description": "Whether the availability status of agent is configured to go offline automatically when away." + }, + "confirmed": { + "type": "boolean", + "description": "Whether the agent has confirmed their email address." + }, + "email": { + "type": "string", + "description": "The email of the agent" + }, + "available_name": { + "type": "string", + "description": "The available name of the agent" + }, + "name": { + "type": "string", + "description": "The name of the agent" + }, + "role": { + "type": "string", + "enum": [ + "agent", + "administrator" + ], + "description": "The role of the agent" + }, + "thumbnail": { + "type": "string", + "description": "The thumbnail of the agent" + }, + "custom_role_id": { + "type": "integer", + "description": "The custom role id of the agent" + } + } + }, + "inbox": { + "type": "object", + "properties": { + "id": { + "type": "number", + "description": "ID of the inbox" + }, + "name": { + "type": "string", + "description": "The name of the inbox" + }, + "website_url": { + "type": "string", + "description": "Website URL" + }, + "channel_type": { + "type": "string", + "description": "The type of the inbox" + }, + "avatar_url": { + "type": "string", + "description": "The avatar image of the inbox" + }, + "widget_color": { + "type": "string", + "description": "Widget Color used for customization of the widget" + }, + "website_token": { + "type": "string", + "description": "Website Token" + }, + "enable_auto_assignment": { + "type": "boolean", + "description": "The flag which shows whether Auto Assignment is enabled or not" + }, + "web_widget_script": { + "type": "string", + "description": "Script used to load the website widget" + }, + "welcome_title": { + "type": "string", + "description": "Welcome title to be displayed on the widget" + }, + "welcome_tagline": { + "type": "string", + "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", + "description": "A greeting message when the user starts the conversation" + }, + "channel_id": { + "type": "number", + "description": "ID of the channel this inbox belongs to" + }, + "working_hours_enabled": { + "type": "boolean", + "description": "The flag which shows whether working hours feature is enabled" + }, + "enable_email_collect": { + "type": "boolean", + "description": "The flag to enable collecting email from contacts" + }, + "csat_survey_enabled": { + "type": "boolean", + "description": "The flag to enable CSAT survey" + }, + "auto_assignment_config": { + "type": "object", + "description": "Configuration settings for auto assignment" + }, + "out_of_office_message": { + "type": "string", + "description": "Message to show when agents are out of office" + }, + "working_hours": { + "type": "array", + "description": "Configuration for working hours of the inbox", + "items": { + "type": "object", + "properties": { + "day_of_week": { + "type": "number", + "description": "Day of the week (0-6, where 0 is Sunday)" + }, + "closed_all_day": { + "type": "boolean", + "description": "Whether the inbox is closed for the entire day" + }, + "open_hour": { + "type": "number", + "description": "Hour when inbox opens (0-23)" + }, + "open_minutes": { + "type": "number", + "description": "Minutes of the hour when inbox opens (0-59)" + }, + "close_hour": { + "type": "number", + "description": "Hour when inbox closes (0-23)" + }, + "close_minutes": { + "type": "number", + "description": "Minutes of the hour when inbox closes (0-59)" + }, + "open_all_day": { + "type": "boolean", + "description": "Whether the inbox is open for the entire day" + } + } + } + }, + "timezone": { + "type": "string", + "description": "Timezone configuration for the inbox" + }, + "callback_webhook_url": { + "type": "string", + "description": "Webhook URL for callbacks" + }, + "allow_messages_after_resolved": { + "type": "boolean", + "description": "Whether to allow messages after a conversation is resolved" + }, + "lock_to_single_conversation": { + "type": "boolean", + "description": "Whether to lock a contact to a single conversation" + }, + "sender_name_type": { + "type": "string", + "description": "Type of sender name to display (e.g., friendly)" + }, + "business_name": { + "type": "string", + "description": "Business name associated with the inbox" + }, + "hmac_mandatory": { + "type": "boolean", + "description": "Whether HMAC verification is mandatory" + }, + "selected_feature_flags": { + "type": "object", + "description": "Selected feature flags for the inbox" + }, + "reply_time": { + "type": "string", + "description": "Expected reply time" + }, + "messaging_service_sid": { + "type": "string", + "description": "Messaging service SID for SMS providers" + }, + "phone_number": { + "type": "string", + "description": "Phone number associated with the inbox" + }, + "medium": { + "type": "string", + "description": "Medium of communication (e.g., sms, email)" + }, + "provider": { + "type": "string", + "description": "Provider of the channel" + } + } + }, + "inbox_contact": { + "type": "object", + "properties": { + "id": { + "type": "number", + "description": "ID of the inbox" + }, + "avatar_url": { + "type": "string", + "description": "The avatar image of the inbox" + }, + "channel_id": { + "type": "number", + "description": "The ID of the channel" + }, + "name": { + "type": "string", + "description": "The name of the inbox" + }, + "channel_type": { + "type": "string", + "description": "The type of the inbox" + }, + "provider": { + "type": "string", + "description": "The provider of the inbox" + } + } + }, + "agent_bot": { + "type": "object", + "properties": { + "id": { + "type": "number", + "description": "ID of the agent bot" + }, + "name": { + "type": "string", + "description": "The name of the agent bot" + }, + "description": { + "type": "string", + "description": "The description about the agent bot" + }, + "thumbnail": { + "type": "string", + "description": "The thumbnail of the agent bot" + }, + "outgoing_url": { + "type": "string", + "description": "The webhook URL for the bot" + }, + "bot_type": { + "type": "string", + "description": "The type of the bot" + }, + "bot_config": { + "type": "object", + "description": "The configuration of the bot" + }, + "account_id": { + "type": "number", + "description": "Account ID if it's an account specific bot" + }, + "access_token": { + "type": "string", + "description": "The access token for the bot" + }, + "system_bot": { + "type": "boolean", + "description": "Whether the bot is a system bot" + } + } + }, + "contact_inboxes": { + "type": "object", + "properties": { + "source_id": { + "type": "string", + "description": "Contact Inbox Source Id" + }, + "inbox": { + "$ref": "#/components/schemas/inbox_contact" + } + } + }, + "contactable_inboxes": { + "type": "object", + "properties": { + "source_id": { + "type": "string", + "description": "Contact Inbox Source Id" + }, + "inbox": { + "$ref": "#/components/schemas/inbox" + } + } + }, + "custom_filter": { + "type": "object", + "properties": { + "id": { + "type": "number", + "description": "The ID of the custom filter" + }, + "name": { + "type": "string", + "description": "The name of the custom filter" + }, + "type": { + "type": "string", + "enum": [ + "conversation", + "contact", + "report" + ], + "description": "The description about the custom filter" + }, + "query": { + "type": "object", + "description": "A query that needs to be saved as a custom filter" + }, + "created_at": { + "type": "string", + "format": "date-time", + "description": "The time at which the custom filter was created" + }, + "updated_at": { + "type": "string", + "format": "date-time", + "description": "The time at which the custom filter was updated" + } + } + }, + "webhook": { + "type": "object", + "properties": { + "id": { + "type": "number", + "description": "The ID of the webhook" + }, + "url": { + "type": "string", + "description": "The url to which the events will be send" + }, + "subscriptions": { + "type": "array", + "items": { + "type": "string", + "enum": [ + "conversation_created", + "conversation_status_changed", + "conversation_updated", + "contact_created", + "contact_updated", + "message_created", + "message_updated", + "webwidget_triggered" + ] + }, + "description": "The list of subscribed events" + }, + "account_id": { + "type": "number", + "description": "The id of the account which the webhook object belongs to" + } + } + }, + "account": { + "type": "object", + "properties": { + "id": { + "type": "number", + "description": "Account ID" + }, + "name": { + "type": "string", + "description": "Name of the account" + }, + "role": { + "type": "string", + "enum": [ + "administrator", + "agent" + ], + "description": "The user role in the account" + } + } + }, + "account_user": { + "type": "array", + "description": "Array of account users", + "items": { + "type": "object", + "properties": { + "account_id": { + "type": "integer", + "description": "The ID of the account" + }, + "user_id": { + "type": "integer", + "description": "The ID of the user" + }, + "role": { + "type": "string", + "description": "whether user is an administrator or agent" + } + } + } + }, + "platform_account": { + "type": "object", + "properties": { + "id": { + "type": "number", + "description": "Account ID" + }, + "name": { + "type": "string", + "description": "Name of the account" + } + } + }, + "team": { + "type": "object", + "properties": { + "id": { + "type": "number", + "description": "The ID of the team" + }, + "name": { + "type": "string", + "description": "The name of the team" + }, + "description": { + "type": "string", + "description": "The description about the team" + }, + "allow_auto_assign": { + "type": "boolean", + "description": "If this setting is turned on, the system would automatically assign the conversation to an agent in the team while assigning the conversation to a team" + }, + "account_id": { + "type": "number", + "description": "The ID of the account with the team is a part of" + }, + "is_member": { + "type": "boolean", + "description": "This field shows whether the current user is a part of the team" + } + } + }, + "integrations_app": { + "type": "object", + "properties": { + "id": { + "type": "string", + "description": "The ID of the integration" + }, + "name": { + "type": "string", + "description": "The name of the integration" + }, + "description": { + "type": "string", + "description": "The description about the team" + }, + "hook_type": { + "type": "string", + "description": "Whether the integration is an account or inbox integration" + }, + "enabled": { + "type": "boolean", + "description": "Whether the integration is enabled for the account" + }, + "allow_multiple_hooks": { + "type": "boolean", + "description": "Whether multiple hooks can be created for the integration" + }, + "hooks": { + "type": "array", + "items": { + "type": "object" + }, + "description": "If there are any hooks created for this integration" + } + } + }, + "integrations_hook": { + "type": "object", + "properties": { + "id": { + "type": "string", + "description": "The ID of the integration hook" + }, + "app_id": { + "type": "string", + "description": "The ID of the integration app" + }, + "inbox_id": { + "type": "string", + "description": "Inbox ID if its an Inbox integration" + }, + "account_id": { + "type": "string", + "description": "Account ID of the integration" + }, + "status": { + "type": "boolean", + "description": "Whether the integration hook is enabled for the account" + }, + "hook_type": { + "type": "boolean", + "description": "Whether its an account or inbox integration hook" + }, + "settings": { + "type": "object", + "description": "The associated settings for the integration" + } + } + }, + "public_contact": { + "type": "object", + "properties": { + "id": { + "type": "integer", + "description": "Id of the contact" + }, + "source_id": { + "type": "string", + "description": "The session identifier of the contact" + }, + "name": { + "type": "string", + "description": "Name of the contact" + }, + "email": { + "type": "string", + "description": "Email of the contact" + }, + "pubsub_token": { + "type": "string", + "description": "The token to be used to connect to chatwoot websocket" + } + } + }, + "public_conversation": { + "type": "object", + "properties": { + "id": { + "type": "integer", + "description": "Id of the conversation" + }, + "inbox_id": { + "type": "string", + "description": "The inbox id of the conversation" + }, + "messages": { + "type": "array", + "items": { + "$ref": "#/components/schemas/message" + }, + "description": "Messages in the conversation" + }, + "contact": { + "type": "object", + "description": "The contact information associated to the conversation" + } + } + }, + "public_message": { + "type": "object", + "properties": { + "id": { + "type": "string", + "description": "Id of the message" + }, + "content": { + "type": "string", + "description": "Text content of the message" + }, + "message_type": { + "type": "string", + "description": "Denotes the message type" + }, + "content_type": { + "type": "string", + "description": "Content type of the message" + }, + "content_attributes": { + "type": "string", + "description": "Additional content attributes of the message" + }, + "created_at": { + "type": "string", + "description": "Created at time stamp of the message" + }, + "conversation_id": { + "type": "string", + "description": "Conversation Id of the message" + }, + "attachments": { + "type": "array", + "items": { + "type": "object" + }, + "description": "Attachments if any" + }, + "sender": { + "type": "object", + "description": "Details of the sender" + } + } + }, + "public_inbox": { + "type": "object", + "properties": { + "identifier": { + "type": "string", + "description": "Inbox identifier" + }, + "name": { + "type": "string", + "description": "Name of the inbox" + }, + "timezone": { + "type": "string", + "description": "The timezone defined on the inbox" + }, + "working_hours": { + "type": "array", + "description": "The working hours defined on the inbox", + "items": { + "type": "object", + "properties": { + "day_of_week": { + "type": "integer", + "description": "Day of the week as a number. Sunday -> 0, Saturday -> 6" + }, + "open_all_day": { + "type": "boolean", + "description": "Whether or not the business is open the whole day" + }, + "closed_all_day": { + "type": "boolean", + "description": "Whether or not the business is closed the whole day" + }, + "open_hour": { + "type": "integer", + "description": "Opening hour. Can be null if closed all day" + }, + "open_minutes": { + "type": "integer", + "description": "Opening minute. Can be null if closed all day" + }, + "close_hour": { + "type": "integer", + "description": "Closing hour. Can be null if closed all day" + }, + "close_minutes": { + "type": "integer", + "description": "Closing minute. Can be null if closed all day" + } + } + } + }, + "working_hours_enabled": { + "type": "boolean", + "description": "Whether of not the working hours are enabled on the inbox" + }, + "csat_survey_enabled": { + "type": "boolean", + "description": "Whether of not the Customer Satisfaction survey is enabled on the inbox" + }, + "greeting_enabled": { + "type": "boolean", + "description": "Whether of not the Greeting Message is enabled on the inbox" + }, + "identity_validation_enabled": { + "type": "boolean", + "description": "Whether of not the User Identity Validation is enforced on the inbox" + } + } + }, + "account_create_update_payload": { + "type": "object", + "properties": { + "name": { + "type": "string", + "description": "Name of the account", + "example": "My Account" + }, + "locale": { + "type": "string", + "description": "The locale of the account", + "example": "en" + }, + "domain": { + "type": "string", + "description": "The domain of the account", + "example": "example.com" + }, + "support_email": { + "type": "string", + "description": "The support email of the account", + "example": "support@example.com" + }, + "status": { + "type": "string", + "enum": [ + "active", + "suspended" + ], + "description": "The status of the account", + "example": "active" + }, + "limits": { + "type": "object", + "description": "The limits of the account", + "example": {} + }, + "custom_attributes": { + "type": "object", + "description": "The custom attributes of the account", + "example": {} + } + } + }, + "account_user_create_update_payload": { + "type": "object", + "required": [ + "user_id", + "role" + ], + "properties": { + "user_id": { + "type": "integer", + "description": "The ID of the user", + "example": 1 + }, + "role": { + "type": "string", + "description": "whether user is an administrator or agent", + "example": "administrator" + } + } + }, + "platform_agent_bot_create_update_payload": { + "type": "object", + "properties": { + "name": { + "type": "string", + "description": "The name of the agent bot", + "example": "My Agent Bot" + }, + "description": { + "type": "string", + "description": "The description of the agent bot", + "example": "This is a sample agent bot" + }, + "outgoing_url": { + "type": "string", + "description": "The webhook URL for the bot", + "example": "https://example.com/webhook" + }, + "account_id": { + "type": "integer", + "description": "The account ID to associate the agent bot with", + "example": 1 + }, + "avatar": { + "type": "string", + "format": "binary", + "description": "Send the form data with the avatar image binary or use the avatar_url" + }, + "avatar_url": { + "type": "string", + "description": "The url to a jpeg, png file for the agent bot avatar", + "example": "https://example.com/avatar.png" + } + } + }, + "agent_bot_create_update_payload": { + "type": "object", + "properties": { + "name": { + "type": "string", + "description": "The name of the agent bot", + "example": "My Agent Bot" + }, + "description": { + "type": "string", + "description": "The description of the agent bot", + "example": "This is a sample agent bot" + }, + "outgoing_url": { + "type": "string", + "description": "The webhook URL for the bot", + "example": "https://example.com/webhook" + }, + "avatar": { + "type": "string", + "format": "binary", + "description": "Send the form data with the avatar image binary or use the avatar_url" + }, + "avatar_url": { + "type": "string", + "description": "The url to a jpeg, png file for the agent bot avatar", + "example": "https://example.com/avatar.png" + }, + "bot_type": { + "type": "integer", + "description": "The type of the bot (0 for webhook)", + "example": 0 + }, + "bot_config": { + "type": "object", + "description": "The configuration for the bot", + "example": {} + } + } + }, + "user_create_update_payload": { + "type": "object", + "properties": { + "name": { + "type": "string", + "description": "Name of the user", + "example": "Daniel" + }, + "display_name": { + "type": "string", + "description": "Display name of the user", + "example": "Dan" + }, + "email": { + "type": "string", + "description": "Email of the user", + "example": "daniel@acme.inc" + }, + "password": { + "type": "string", + "description": "Password must contain uppercase, lowercase letters, number and a special character", + "example": "Password2!" + }, + "custom_attributes": { + "type": "object", + "description": "Custom attributes you want to associate with the user", + "example": {} + } + } + }, + "canned_response_create_update_payload": { + "type": "object", + "properties": { + "content": { + "type": "string", + "description": "Message content for canned response", + "example": "Hello, {{contact.name}}! Welcome to our service." + }, + "short_code": { + "type": "string", + "description": "Short Code for quick access of the canned response", + "example": "welcome" + } + } + }, + "custom_attribute_create_update_payload": { + "type": "object", + "properties": { + "attribute_display_name": { + "type": "string", + "description": "Attribute display name", + "example": "Custom Attribute" + }, + "attribute_display_type": { + "type": "integer", + "description": "Attribute display type (text- 0, number- 1, currency- 2, percent- 3, link- 4, date- 5, list- 6, checkbox- 7)", + "example": 0 + }, + "attribute_description": { + "type": "string", + "description": "Attribute description", + "example": "This is a custom attribute" + }, + "attribute_key": { + "type": "string", + "description": "Attribute unique key value", + "example": "custom_attribute" + }, + "attribute_values": { + "type": "array", + "description": "Attribute values", + "items": { + "type": "string" + }, + "example": [ + "value1", + "value2" + ] + }, + "attribute_model": { + "type": "integer", + "description": "Attribute type(conversation_attribute- 0, contact_attribute- 1)", + "example": 0 + }, + "regex_pattern": { + "type": "string", + "description": "Regex pattern (Only applicable for type- text). The regex pattern is used to validate the attribute value(s).", + "example": "^[a-zA-Z0-9]+$" + }, + "regex_cue": { + "type": "string", + "description": "Regex cue message (Only applicable for type- text). The cue message is shown when the regex pattern is not matched.", + "example": "Please enter a valid value" + } + } + }, + "agent_create_payload": { + "type": "object", + "required": [ + "name", + "email", + "role" + ], + "properties": { + "name": { + "type": "string", + "description": "Full Name of the agent", + "example": "John Doe" + }, + "email": { + "type": "string", + "description": "Email of the Agent", + "example": "john.doe@acme.inc" + }, + "role": { + "type": "string", + "enum": [ + "agent", + "administrator" + ], + "description": "Whether its administrator or agent", + "example": "agent" + }, + "availability_status": { + "type": "string", + "enum": [ + "available", + "busy", + "offline" + ], + "description": "The availability setting of the agent.", + "example": "available" + }, + "auto_offline": { + "type": "boolean", + "description": "Whether the availability status of agent is configured to go offline automatically when away.", + "example": true + } + } + }, + "agent_update_payload": { + "type": "object", + "required": [ + "role" + ], + "properties": { + "role": { + "type": "string", + "enum": [ + "agent", + "administrator" + ], + "description": "Whether its administrator or agent", + "example": "agent" + }, + "availability_status": { + "type": "string", + "enum": [ + "available", + "busy", + "offline" + ], + "description": "The availability status of the agent.", + "example": "available" + }, + "auto_offline": { + "type": "boolean", + "description": "Whether the availability status of agent is configured to go offline automatically when away.", + "example": true + } + } + }, + "contact_create_payload": { + "type": "object", + "required": [ + "inbox_id" + ], + "properties": { + "inbox_id": { + "type": "number", + "description": "ID of the inbox to which the contact belongs", + "example": 1 + }, + "name": { + "type": "string", + "description": "name of the contact", + "example": "Alice" + }, + "email": { + "type": "string", + "description": "email of the contact", + "example": "alice@acme.inc" + }, + "blocked": { + "type": "boolean", + "description": "whether the contact is blocked or not", + "example": false + }, + "phone_number": { + "type": "string", + "description": "phone number of the contact", + "example": "+123456789" + }, + "avatar": { + "type": "string", + "format": "binary", + "description": "Send the form data with the avatar image binary or use the avatar_url" + }, + "avatar_url": { + "type": "string", + "description": "The url to a jpeg, png file for the contact avatar", + "example": "https://example.com/avatar.png" + }, + "identifier": { + "type": "string", + "description": "A unique identifier for the contact in external system", + "example": "1234567890" + }, + "additional_attributes": { + "type": "object", + "description": "An object where you can store additional attributes for contact. example {\"type\":\"customer\", \"age\":30}", + "example": { + "type": "customer", + "age": 30 + } + }, + "custom_attributes": { + "type": "object", + "description": "An object where you can store custom attributes for contact. example {\"type\":\"customer\", \"age\":30}, this should have a valid custom attribute definition.", + "example": {} + } + } + }, + "contact_update_payload": { + "type": "object", + "properties": { + "name": { + "type": "string", + "description": "name of the contact", + "example": "Alice" + }, + "email": { + "type": "string", + "description": "email of the contact", + "example": "alice@acme.inc" + }, + "blocked": { + "type": "boolean", + "description": "whether the contact is blocked or not", + "example": false + }, + "phone_number": { + "type": "string", + "description": "phone number of the contact", + "example": "+123456789" + }, + "avatar": { + "type": "string", + "format": "binary", + "description": "Send the form data with the avatar image binary or use the avatar_url" + }, + "avatar_url": { + "type": "string", + "description": "The url to a jpeg, png file for the contact avatar", + "example": "https://example.com/avatar.png" + }, + "identifier": { + "type": "string", + "description": "A unique identifier for the contact in external system", + "example": "1234567890" + }, + "additional_attributes": { + "type": "object", + "description": "An object where you can store additional attributes for contact. example {\"type\":\"customer\", \"age\":30}", + "example": { + "type": "customer", + "age": 30 + } + }, + "custom_attributes": { + "type": "object", + "description": "An object where you can store custom attributes for contact. example {\"type\":\"customer\", \"age\":30}, this should have a valid custom attribute definition.", + "example": {} + } + } + }, + "conversation_create_payload": { + "type": "object", + "required": [ + "source_id", + "inbox_id" + ], + "properties": { + "source_id": { + "type": "string", + "description": "Conversation source id", + "example": "1234567890" + }, + "inbox_id": { + "type": "integer", + "description": "Id of inbox in which the conversation is created Allowed Inbox Types: Website, Phone, Api, Email", + "example": 1 + }, + "contact_id": { + "type": "integer", + "description": "Contact Id for which conversation is created", + "example": 1 + }, + "additional_attributes": { + "type": "object", + "description": "Lets you specify attributes like browser information", + "example": { + "browser": "Chrome", + "browser_version": "89.0.4389.82", + "os": "Windows", + "os_version": "10" + } + }, + "custom_attributes": { + "type": "object", + "description": "The object to save custom attributes for conversation, accepts custom attributes key and value", + "example": { + "attribute_key": "attribute_value", + "priority_conversation_number": 3 + } + }, + "status": { + "type": "string", + "enum": [ + "open", + "resolved", + "pending" + ], + "description": "Specify the conversation whether it's pending, open, closed", + "example": "open" + }, + "assignee_id": { + "type": "integer", + "description": "Agent Id for assigning a conversation to an agent", + "example": 1 + }, + "team_id": { + "type": "integer", + "description": "Team Id for assigning a conversation to a team\\", + "example": 1 + }, + "snoozed_until": { + "type": "string", + "format": "date-time", + "description": "Snoozed until date time", + "example": "2030-07-21T17:32:28Z" + }, + "message": { + "type": "object", + "description": "The initial message to be sent to the conversation", + "required": [ + "content" + ], + "properties": { + "content": { + "type": "string", + "description": "The content of the message", + "example": "Hello, how can I help you?" + }, + "template_params": { + "type": "object", + "description": "The template params for the message in case of whatsapp Channel", + "properties": { + "name": { + "type": "string", + "description": "Name of the template", + "example": "sample_issue_resolution" + }, + "category": { + "type": "string", + "description": "Category of the template", + "example": "UTILITY" + }, + "language": { + "type": "string", + "description": "Language of the template", + "example": "en_US" + }, + "processed_params": { + "type": "object", + "description": "The processed param values for template variables in template", + "example": { + "1": "Chatwoot" + } + } + } + } + } + } + } + }, + "conversation_message_create_payload": { + "type": "object", + "required": [ + "content" + ], + "properties": { + "content": { + "type": "string", + "description": "The content of the message", + "example": "Hello, how can I help you?" + }, + "message_type": { + "type": "string", + "enum": [ + "outgoing", + "incoming" + ], + "description": "The type of the message", + "example": "outgoing" + }, + "private": { + "type": "boolean", + "description": "Flag to identify if it is a private note", + "example": false + }, + "content_type": { + "type": "string", + "enum": [ + "text", + "input_email", + "cards", + "input_select", + "form", + "article" + ], + "description": "Content type of the message", + "example": "text" + }, + "content_attributes": { + "type": "object", + "description": "Attributes based on the content type", + "example": {} + }, + "campaign_id": { + "type": "integer", + "description": "The campaign id to which the message belongs", + "example": 1 + }, + "template_params": { + "type": "object", + "description": "The template params for the message in case of whatsapp Channel", + "properties": { + "name": { + "type": "string", + "description": "Name of the template", + "example": "sample_issue_resolution" + }, + "category": { + "type": "string", + "description": "Category of the template", + "example": "UTILITY" + }, + "language": { + "type": "string", + "description": "Language of the template", + "example": "en_US" + }, + "processed_params": { + "type": "object", + "description": "The processed param values for template variables in template", + "example": { + "1": "Chatwoot" + } + } + } + } + } + }, + "inbox_create_payload": { + "type": "object", + "properties": { + "name": { + "type": "string", + "description": "The name of the inbox", + "example": "Support" + }, + "avatar": { + "type": "string", + "format": "binary", + "description": "Image file for avatar" + }, + "greeting_enabled": { + "type": "boolean", + "description": "Enable greeting message", + "example": true + }, + "greeting_message": { + "type": "string", + "description": "Greeting message to be displayed on the widget", + "example": "Hello, how can I help you?" + }, + "enable_email_collect": { + "type": "boolean", + "description": "Enable email collection", + "example": true + }, + "csat_survey_enabled": { + "type": "boolean", + "description": "Enable CSAT survey", + "example": true + }, + "enable_auto_assignment": { + "type": "boolean", + "description": "Enable Auto Assignment", + "example": true + }, + "working_hours_enabled": { + "type": "boolean", + "description": "Enable working hours", + "example": true + }, + "out_of_office_message": { + "type": "string", + "description": "Out of office message to be displayed on the widget", + "example": "We are currently out of office. Please leave a message and we will get back to you." + }, + "timezone": { + "type": "string", + "description": "Timezone of the inbox", + "example": "America/New_York" + }, + "allow_messages_after_resolved": { + "type": "boolean", + "description": "Allow messages after conversation is resolved", + "example": true + }, + "lock_to_single_conversation": { + "type": "boolean", + "description": "Lock to single conversation", + "example": true + }, + "portal_id": { + "type": "integer", + "description": "Id of the help center portal to attach to the inbox", + "example": 1 + }, + "sender_name_type": { + "type": "string", + "description": "Sender name type for the inbox", + "enum": [ + "friendly", + "professional" + ], + "example": "friendly" + }, + "business_name": { + "type": "string", + "description": "Business name for the inbox", + "example": "My Business" + }, + "channel": { + "type": "object", + "properties": { + "type": { + "type": "string", + "description": "Type of the channel", + "enum": [ + "web_widget", + "api", + "email", + "line", + "telegram", + "whatsapp", + "sms" + ], + "example": "web_widget" + }, + "website_url": { + "type": "string", + "description": "URL at which the widget will be loaded", + "example": "https://example.com" + }, + "welcome_title": { + "type": "string", + "description": "Welcome title to be displayed on the widget", + "example": "Welcome to our support" + }, + "welcome_tagline": { + "type": "string", + "description": "Welcome tagline to be displayed on the widget", + "example": "We are here to help you" + }, + "widget_color": { + "type": "string", + "description": "A Hex-color string used to customize the widget", + "example": "#FF5733" + } + } + } + } + }, + "inbox_update_payload": { + "type": "object", + "properties": { + "name": { + "type": "string", + "description": "The name of the inbox", + "example": "Support" + }, + "avatar": { + "type": "string", + "format": "binary", + "description": "Image file for avatar" + }, + "greeting_enabled": { + "type": "boolean", + "description": "Enable greeting message", + "example": true + }, + "greeting_message": { + "type": "string", + "description": "Greeting message to be displayed on the widget", + "example": "Hello, how can I help you?" + }, + "enable_email_collect": { + "type": "boolean", + "description": "Enable email collection", + "example": true + }, + "csat_survey_enabled": { + "type": "boolean", + "description": "Enable CSAT survey", + "example": true + }, + "enable_auto_assignment": { + "type": "boolean", + "description": "Enable Auto Assignment", + "example": true + }, + "working_hours_enabled": { + "type": "boolean", + "description": "Enable working hours", + "example": true + }, + "out_of_office_message": { + "type": "string", + "description": "Out of office message to be displayed on the widget", + "example": "We are currently out of office. Please leave a message and we will get back to you." + }, + "timezone": { + "type": "string", + "description": "Timezone of the inbox", + "example": "America/New_York" + }, + "allow_messages_after_resolved": { + "type": "boolean", + "description": "Allow messages after conversation is resolved", + "example": true + }, + "lock_to_single_conversation": { + "type": "boolean", + "description": "Lock to single conversation", + "example": true + }, + "portal_id": { + "type": "integer", + "description": "Id of the help center portal to attach to the inbox", + "example": 1 + }, + "sender_name_type": { + "type": "string", + "description": "Sender name type for the inbox", + "enum": [ + "friendly", + "professional" + ], + "example": "friendly" + }, + "business_name": { + "type": "string", + "description": "Business name for the inbox", + "example": "My Business" + }, + "channel": { + "type": "object", + "properties": { + "website_url": { + "type": "string", + "description": "URL at which the widget will be loaded", + "example": "https://example.com" + }, + "welcome_title": { + "type": "string", + "description": "Welcome title to be displayed on the widget", + "example": "Welcome to our support" + }, + "welcome_tagline": { + "type": "string", + "description": "Welcome tagline to be displayed on the widget", + "example": "We are here to help you" + }, + "widget_color": { + "type": "string", + "description": "A Hex-color string used to customize the widget", + "example": "#FF5733" + } + } + } + } + }, + "team_create_update_payload": { + "type": "object", + "properties": { + "name": { + "type": "string", + "description": "The name of the team", + "example": "Support Team" + }, + "description": { + "type": "string", + "description": "The description of the team", + "example": "This is a team of support agents" + }, + "allow_auto_assign": { + "type": "boolean", + "description": "If this setting is turned on, the system would automatically assign the conversation to an agent in the team while assigning the conversation to a team", + "example": true + } + } + }, + "custom_filter_create_update_payload": { + "type": "object", + "properties": { + "name": { + "type": "string", + "description": "The name of the custom filter", + "example": "My Custom Filter" + }, + "type": { + "type": "string", + "enum": [ + "conversation", + "contact", + "report" + ], + "description": "The description about the custom filter", + "example": "conversation" + }, + "query": { + "type": "object", + "description": "A query that needs to be saved as a custom filter", + "example": {} + } + } + }, + "webhook_create_update_payload": { + "type": "object", + "properties": { + "url": { + "type": "string", + "description": "The url where the events should be sent", + "example": "https://example.com/webhook" + }, + "subscriptions": { + "type": "array", + "items": { + "type": "string", + "enum": [ + "conversation_created", + "conversation_status_changed", + "conversation_updated", + "message_created", + "message_updated", + "contact_created", + "contact_updated", + "webwidget_triggered" + ] + }, + "description": "The events you want to subscribe to.", + "example": [ + "conversation_created", + "conversation_status_changed" + ] + } + } + }, + "integrations_hook_create_payload": { + "type": "object", + "properties": { + "app_id": { + "type": "integer", + "description": "The ID of app for which integration hook is being created", + "example": 1 + }, + "inbox_id": { + "type": "integer", + "description": "The inbox ID, if the hook is an inbox hook", + "example": 1 + }, + "status": { + "type": "integer", + "description": "The status of the integration (0 for inactive, 1 for active)", + "example": 1 + }, + "settings": { + "type": "object", + "description": "The settings required by the integration", + "example": {} + } + } + }, + "integrations_hook_update_payload": { + "type": "object", + "properties": { + "status": { + "type": "integer", + "description": "The status of the integration (0 for inactive, 1 for active)", + "example": 1 + }, + "settings": { + "type": "object", + "description": "The settings required by the integration", + "example": {} + } + } + }, + "automation_rule_create_update_payload": { + "type": "object", + "properties": { + "name": { + "type": "string", + "description": "Rule name", + "example": "Add label on message create event" + }, + "description": { + "type": "string", + "description": "The description about the automation and actions", + "example": "Add label support and sales on message create event if incoming message content contains text help" + }, + "event_name": { + "type": "string", + "enum": [ + "conversation_created", + "conversation_updated", + "message_created" + ], + "example": "message_created", + "description": "The event when you want to execute the automation actions" + }, + "active": { + "type": "boolean", + "description": "Enable/disable automation rule" + }, + "actions": { + "type": "array", + "description": "Array of actions which you want to perform when condition matches, e.g add label support if message contains content help.", + "items": { + "type": "object", + "example": { + "action_name": "add_label", + "action_params": [ + "support" + ] + } + } + }, + "conditions": { + "type": "array", + "description": "Array of conditions on which conversation filter would work, e.g message content contains text help.", + "items": { + "type": "object", + "example": { + "attribute_key": "content", + "filter_operator": "contains", + "query_operator": "OR", + "values": [ + "help" + ] + } + } + } + } + }, + "portal_create_update_payload": { + "type": "object", + "properties": { + "color": { + "type": "string", + "description": "Header color for help-center in hex format", + "example": "#FFFFFF" + }, + "custom_domain": { + "type": "string", + "description": "Custom domain to display help center.", + "example": "chatwoot.help" + }, + "header_text": { + "type": "string", + "description": "Help center header", + "example": "Handbook" + }, + "homepage_link": { + "type": "string", + "description": "link to main dashboard", + "example": "https://www.chatwoot.com/" + }, + "name": { + "type": "string", + "description": "Name for the portal", + "example": "Handbook" + }, + "page_title": { + "type": "string", + "description": "Page title for the portal", + "example": "Handbook" + }, + "slug": { + "type": "string", + "description": "Slug for the portal to display in link", + "example": "handbook" + }, + "archived": { + "type": "boolean", + "description": "Status to check if portal is live", + "example": false + }, + "config": { + "type": "object", + "description": "Configuration about supporting locales", + "example": { + "allowed_locales": [ + "en", + "es" + ], + "default_locale": "en" + } + } + } + }, + "category_create_update_payload": { + "type": "object", + "properties": { + "name": { + "type": "string", + "description": "The name of the category", + "example": "Category Name" + }, + "description": { + "type": "string", + "description": "A description for the category", + "example": "Category description" + }, + "position": { + "type": "integer", + "description": "Category position in the portal list to sort", + "example": 1 + }, + "slug": { + "type": "string", + "description": "The category slug used in the URL", + "example": "category-name" + }, + "locale": { + "type": "string", + "description": "The locale of the category", + "example": "en" + }, + "icon": { + "type": "string", + "description": "The icon of the category as a string (emoji)", + "example": "📚" + }, + "parent_category_id": { + "type": "integer", + "description": "To define parent category, e.g product documentation has multiple level features in sales category or in engineering category.", + "example": 1 + }, + "associated_category_id": { + "type": "integer", + "description": "To associate similar categories to each other, e.g same category of product documentation in different languages", + "example": 2 + } + } + }, + "article_create_update_payload": { + "type": "object", + "properties": { + "title": { + "type": "string", + "description": "The title of the article", + "example": "Article Title" + }, + "slug": { + "type": "string", + "description": "The slug of the article", + "example": "article-title" + }, + "position": { + "type": "integer", + "description": "article position in category", + "example": 1 + }, + "content": { + "type": "string", + "description": "The text content.", + "example": "This is the content of the article" + }, + "description": { + "type": "string", + "description": "The description of the article", + "example": "This is the description of the article" + }, + "category_id": { + "type": "integer", + "description": "The category id of the article", + "example": 1 + }, + "author_id": { + "type": "integer", + "description": "The author agent id of the article", + "example": 1 + }, + "associated_article_id": { + "type": "integer", + "description": "To associate similar articles to each other, e.g to provide the link for the reference.", + "example": 2 + }, + "status": { + "type": "integer", + "description": "The status of the article. 0 for draft, 1 for published, 2 for archived", + "example": 1 + }, + "locale": { + "type": "string", + "description": "The locale of the article", + "example": "en" + }, + "meta": { + "type": "object", + "description": "Use for search", + "example": { + "tags": [ + "article_name" + ], + "title": "article title", + "description": "article description" + } + } + } + }, + "public_contact_create_update_payload": { + "type": "object", + "properties": { + "identifier": { + "type": "string", + "description": "External identifier of the contact", + "example": "1234567890" + }, + "identifier_hash": { + "type": "string", + "description": "Identifier hash prepared for HMAC authentication", + "example": "e93275d4eba0e5679ad55f5360af00444e2a888df9b0afa3e8b691c3173725f9" + }, + "email": { + "type": "string", + "description": "Email of the contact", + "example": "alice@acme.inc" + }, + "name": { + "type": "string", + "description": "Name of the contact", + "example": "Alice" + }, + "phone_number": { + "type": "string", + "description": "Phone number of the contact", + "example": "+123456789" + }, + "avatar": { + "type": "string", + "format": "binary", + "description": "Send the form data with the avatar image binary or use the avatar_url" + }, + "custom_attributes": { + "type": "object", + "description": "Custom attributes of the customer", + "example": {} + } + } + }, + "public_message_create_payload": { + "type": "object", + "properties": { + "content": { + "type": "string", + "description": "Content for the message", + "example": "Hello, how can I help you?" + }, + "echo_id": { + "type": "string", + "description": "Temporary identifier which will be passed back via websockets", + "example": "1234567890" + } + } + }, + "public_message_update_payload": { + "type": "object", + "properties": { + "submitted_values": { + "type": "object", + "description": "Replies to the Bot Message Types", + "properties": { + "name": { + "type": "string", + "description": "The name of the submiitted value", + "example": "My Name" + }, + "title": { + "type": "string", + "description": "The title of the submitted value", + "example": "My Title" + }, + "value": { + "type": "string", + "description": "The value of the submitted value", + "example": "value" + }, + "csat_survey_response": { + "type": "object", + "description": "The CSAT survey response", + "properties": { + "feedback_message": { + "type": "string", + "description": "The feedback message of the CSAT survey response", + "example": "Great service!" + }, + "rating": { + "type": "integer", + "description": "The rating of the CSAT survey response", + "example": 5 + } + } + } + } + } + } + }, + "public_conversation_create_payload": { + "type": "object", + "properties": { + "custom_attributes": { + "type": "object", + "description": "Custom attributes of the conversation", + "example": {} + } + } + }, + "extended_contact": { + "allOf": [ + { + "$ref": "#/components/schemas/contact" + }, + { + "type": "object", + "properties": { + "id": { + "type": "number", + "description": "Id of the user" + }, + "availability_status": { + "type": "string", + "enum": [ + "online", + "offline" + ], + "description": "Availability status of the user" + } + } + } + ] + }, + "contact_base": { + "allOf": [ + { + "$ref": "#/components/schemas/generic_id" + }, + { + "$ref": "#/components/schemas/contact" + } + ] + }, + "contact_list": { + "type": "array", + "description": "array of contacts", + "items": { + "allOf": [ + { + "$ref": "#/components/schemas/contact" + } + ] + } + }, + "contact_conversations": { + "type": "array", + "description": "array of conversations", + "items": { + "allOf": [ + { + "$ref": "#/components/schemas/conversation" + }, + { + "type": "object", + "properties": { + "meta": { + "type": "object", + "properties": { + "sender": { + "type": "object", + "properties": { + "additional_attributes": { + "type": "object", + "description": "The additional attributes of the sender" + }, + "availability_status": { + "type": "string", + "description": "The availability status of the sender" + }, + "email": { + "type": "string", + "description": "The email of the sender" + }, + "id": { + "type": "number", + "description": "ID fo the sender" + }, + "name": { + "type": "string", + "description": "The name of the sender" + }, + "phone_number": { + "type": "string", + "description": "The phone number of the sender" + }, + "blocked": { + "type": "boolean", + "description": "Whether the sender is blocked" + }, + "identifier": { + "type": "string", + "description": "The identifier of the sender" + }, + "thumbnail": { + "type": "string", + "description": "Avatar URL of the contact" + }, + "custom_attributes": { + "type": "object", + "description": "The custom attributes of the sender" + }, + "last_activity_at": { + "type": "number", + "description": "The last activity at of the sender" + }, + "created_at": { + "type": "number", + "description": "The created at of the sender" + } + } + }, + "channel": { + "type": "string", + "description": "Channel Type" + }, + "assignee": { + "$ref": "#/components/schemas/user" + }, + "hmac_verified": { + "type": "boolean", + "description": "Whether the hmac is verified" + } + } + } + } + }, + { + "type": "object", + "properties": { + "display_id": { + "type": "number" + } + } + } + ] + } + }, + "contact_labels": { + "type": "object", + "properties": { + "payload": { + "type": "array", + "description": "Array of labels", + "items": { + "type": "string" + } + } + } + }, + "conversation_list": { + "type": "object", + "properties": { + "data": { + "type": "object", + "properties": { + "meta": { + "type": "object", + "properties": { + "mine_count": { + "type": "number" + }, + "unassigned_count": { + "type": "number" + }, + "assigned_count": { + "type": "number" + }, + "all_count": { + "type": "number" + } + } + }, + "payload": { + "type": "array", + "description": "array of conversations", + "items": { + "allOf": [ + { + "$ref": "#/components/schemas/generic_id" + }, + { + "$ref": "#/components/schemas/conversation" + }, + { + "type": "object", + "properties": { + "meta": { + "type": "object", + "properties": { + "sender": { + "type": "object", + "properties": { + "additional_attributes": { + "type": "object", + "description": "The additional attributes of the sender" + }, + "availability_status": { + "type": "string", + "description": "The availability status of the sender" + }, + "email": { + "type": "string", + "description": "The email of the sender" + }, + "id": { + "type": "number", + "description": "ID fo the sender" + }, + "name": { + "type": "string", + "description": "The name of the sender" + }, + "phone_number": { + "type": "string", + "description": "The phone number of the sender" + }, + "blocked": { + "type": "boolean", + "description": "Whether the sender is blocked" + }, + "identifier": { + "type": "string", + "description": "The identifier of the sender" + }, + "thumbnail": { + "type": "string", + "description": "Avatar URL of the contact" + }, + "custom_attributes": { + "type": "object", + "description": "The custom attributes of the sender" + }, + "last_activity_at": { + "type": "number", + "description": "The last activity at of the sender" + }, + "created_at": { + "type": "number", + "description": "The created at of the sender" + } + } + }, + "channel": { + "type": "string", + "description": "Channel Type" + }, + "assignee": { + "$ref": "#/components/schemas/user" + }, + "hmac_verified": { + "type": "boolean", + "description": "Whether the hmac is verified" + } + } + } + } + } + ] + } + } + } + } + } + }, + "conversation_show": { + "type": "object", + "allOf": [ + { + "$ref": "#/components/schemas/conversation" + }, + { + "type": "object", + "properties": { + "meta": { + "type": "object", + "properties": { + "sender": { + "type": "object", + "properties": { + "additional_attributes": { + "type": "object", + "description": "The additional attributes of the sender" + }, + "availability_status": { + "type": "string", + "description": "The availability status of the sender" + }, + "email": { + "type": "string", + "description": "The email of the sender" + }, + "id": { + "type": "number", + "description": "ID fo the sender" + }, + "name": { + "type": "string", + "description": "The name of the sender" + }, + "phone_number": { + "type": "string", + "description": "The phone number of the sender" + }, + "blocked": { + "type": "boolean", + "description": "Whether the sender is blocked" + }, + "identifier": { + "type": "string", + "description": "The identifier of the sender" + }, + "thumbnail": { + "type": "string", + "description": "Avatar URL of the contact" + }, + "custom_attributes": { + "type": "object", + "description": "The custom attributes of the sender" + }, + "last_activity_at": { + "type": "number", + "description": "The last activity at of the sender" + }, + "created_at": { + "type": "number", + "description": "The created at of the sender" + } + } + }, + "channel": { + "type": "string", + "description": "Channel Type" + }, + "assignee": { + "$ref": "#/components/schemas/user" + }, + "hmac_verified": { + "type": "boolean", + "description": "Whether the hmac is verified" + } + } + } + } + } + ] + }, + "conversation_status_toggle": { + "type": "object", + "properties": { + "meta": { + "type": "object" + }, + "payload": { + "type": "object", + "properties": { + "success": { + "type": "boolean" + }, + "current_status": { + "type": "string", + "enum": [ + "open", + "resolved" + ] + }, + "conversation_id": { + "type": "number" + } + } + } + } + }, + "conversation_labels": { + "type": "object", + "properties": { + "payload": { + "type": "array", + "description": "Array of labels", + "items": { + "type": "string" + } + } + } + }, + "account_summary": { + "type": "object", + "properties": { + "avg_first_response_time": { + "type": "string" + }, + "avg_resolution_time": { + "type": "string" + }, + "conversations_count": { + "type": "number" + }, + "incoming_messages_count": { + "type": "number" + }, + "outgoing_messages_count": { + "type": "number" + }, + "resolutions_count": { + "type": "number" + }, + "previous": { + "type": "object", + "properties": { + "avg_first_response_time": { + "type": "string" + }, + "avg_resolution_time": { + "type": "string" + }, + "conversations_count": { + "type": "number" + }, + "incoming_messages_count": { + "type": "number" + }, + "outgoing_messages_count": { + "type": "number" + }, + "resolutions_count": { + "type": "number" + } + } + } + } + }, + "agent_conversation_metrics": { + "type": "object", + "properties": { + "id": { + "type": "number" + }, + "name": { + "type": "string" + }, + "email": { + "type": "string" + }, + "thumbnail": { + "type": "string" + }, + "availability": { + "type": "string" + }, + "metric": { + "type": "object", + "properties": { + "open": { + "type": "number" + }, + "unattended": { + "type": "number" + } + } + } + } + }, + "contact_detail": { + "type": "object", + "properties": { + "additional_attributes": { + "type": "object", + "description": "The object containing additional attributes related to the contact", + "properties": { + "city": { + "type": "string", + "description": "City of the contact" + }, + "country": { + "type": "string", + "description": "Country of the contact" + }, + "country_code": { + "type": "string", + "description": "Country code of the contact" + }, + "created_at_ip": { + "type": "string", + "description": "IP address when the contact was created" + } + } + }, + "custom_attributes": { + "type": "object", + "description": "The custom attributes of the contact" + }, + "email": { + "type": "string", + "description": "The email address of the contact" + }, + "id": { + "type": "integer", + "description": "The ID of the contact" + }, + "identifier": { + "type": "string", + "description": "The identifier of the contact", + "nullable": true + }, + "name": { + "type": "string", + "description": "The name of the contact" + }, + "phone_number": { + "type": "string", + "description": "The phone number of the contact", + "nullable": true + }, + "thumbnail": { + "type": "string", + "description": "The thumbnail of the contact" + }, + "blocked": { + "type": "boolean", + "description": "Whether the contact is blocked" + }, + "type": { + "type": "string", + "description": "The type of entity", + "enum": [ + "contact" + ] + } + } + }, + "message_detailed": { + "type": "object", + "properties": { + "id": { + "type": "number", + "description": "The ID of the message" + }, + "content": { + "type": "string", + "description": "The text content of the message" + }, + "inbox_id": { + "type": "number", + "description": "The ID of the inbox" + }, + "conversation_id": { + "type": "number", + "description": "The ID of the conversation" + }, + "message_type": { + "type": "integer", + "enum": [ + 0, + 1, + 2, + 3 + ], + "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" + ], + "description": "The type of the message content" + }, + "status": { + "type": "string", + "enum": [ + "sent", + "delivered", + "read", + "failed" + ], + "description": "The status of the message" + }, + "content_attributes": { + "type": "object", + "description": "The content attributes for each content_type", + "properties": { + "in_reply_to": { + "type": "string", + "description": "ID of the message this is replying to", + "nullable": true + } + } + }, + "created_at": { + "type": "integer", + "description": "The timestamp when message was created" + }, + "private": { + "type": "boolean", + "description": "The flag which shows whether the message is private or not" + }, + "source_id": { + "type": "string", + "description": "The source ID of the message", + "nullable": true + }, + "sender": { + "$ref": "#/components/schemas/contact_detail" + } + } + }, + "conversation_meta": { + "type": "object", + "properties": { + "labels": { + "type": "array", + "items": { + "type": "string" + }, + "description": "Labels associated with the conversation" + }, + "additional_attributes": { + "type": "object", + "properties": { + "browser": { + "type": "object", + "properties": { + "device_name": { + "type": "string", + "description": "Name of the device" + }, + "browser_name": { + "type": "string", + "description": "Name of the browser" + }, + "platform_name": { + "type": "string", + "description": "Name of the platform" + }, + "browser_version": { + "type": "string", + "description": "Version of the browser" + }, + "platform_version": { + "type": "string", + "description": "Version of the platform" + } + } + }, + "referer": { + "type": "string", + "description": "Referrer URL" + }, + "initiated_at": { + "type": "object", + "properties": { + "timestamp": { + "type": "string", + "description": "Timestamp when the conversation was initiated" + } + } + }, + "browser_language": { + "type": "string", + "description": "Browser language setting" + }, + "conversation_language": { + "type": "string", + "description": "Conversation language" + } + }, + "description": "Additional attributes of the conversation" + }, + "contact": { + "$ref": "#/components/schemas/contact_detail" + }, + "agent_last_seen_at": { + "type": "string", + "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 + } + } + }, + "conversation_messages": { + "type": "object", + "properties": { + "meta": { + "$ref": "#/components/schemas/conversation_meta" + }, + "payload": { + "type": "array", + "items": { + "$ref": "#/components/schemas/message_detailed" + }, + "description": "List of messages in the conversation" + } + } + }, + "contact_meta": { + "type": "object", + "properties": { + "count": { + "type": "integer", + "description": "Total number of contacts" + }, + "current_page": { + "type": "string", + "description": "Current page number" + } + } + }, + "contact_inbox": { + "type": "object", + "properties": { + "source_id": { + "type": "string", + "description": "Source identifier for the contact inbox" + }, + "inbox": { + "type": "object", + "properties": { + "id": { + "type": "integer", + "description": "ID of the inbox" + }, + "avatar_url": { + "type": "string", + "description": "URL for the inbox avatar" + }, + "channel_id": { + "type": "integer", + "description": "ID of the channel" + }, + "name": { + "type": "string", + "description": "Name of the inbox" + }, + "channel_type": { + "type": "string", + "description": "Type of channel" + }, + "provider": { + "type": "string", + "description": "Provider of the inbox", + "nullable": true + } + } + } + } + }, + "contact_list_item": { + "type": "object", + "properties": { + "additional_attributes": { + "type": "object", + "description": "The object containing additional attributes related to the contact", + "properties": { + "city": { + "type": "string", + "description": "City of the contact" + }, + "country": { + "type": "string", + "description": "Country of the contact" + }, + "country_code": { + "type": "string", + "description": "Country code of the contact" + }, + "created_at_ip": { + "type": "string", + "description": "IP address when the contact was created" + } + } + }, + "availability_status": { + "type": "string", + "description": "Availability status of the contact", + "enum": [ + "online", + "offline" + ] + }, + "email": { + "type": "string", + "description": "The email address of the contact", + "nullable": true + }, + "id": { + "type": "integer", + "description": "The ID of the contact" + }, + "name": { + "type": "string", + "description": "The name of the contact" + }, + "phone_number": { + "type": "string", + "description": "The phone number of the contact", + "nullable": true + }, + "blocked": { + "type": "boolean", + "description": "Whether the contact is blocked" + }, + "identifier": { + "type": "string", + "description": "The identifier of the contact", + "nullable": true + }, + "thumbnail": { + "type": "string", + "description": "The thumbnail of the contact" + }, + "custom_attributes": { + "type": "object", + "description": "The custom attributes of the contact" + }, + "last_activity_at": { + "type": "integer", + "description": "Timestamp of last activity", + "nullable": true + }, + "created_at": { + "type": "integer", + "description": "Timestamp when contact was created" + }, + "contact_inboxes": { + "type": "array", + "description": "List of inboxes associated with this contact", + "items": { + "$ref": "#/components/schemas/contact_inbox" + } + } + } + }, + "contacts_list_response": { + "type": "object", + "properties": { + "meta": { + "$ref": "#/components/schemas/contact_meta" + }, + "payload": { + "type": "array", + "items": { + "$ref": "#/components/schemas/contact_list_item" + }, + "description": "List of contacts" + } + } + }, + "contact_show_response": { + "type": "object", + "properties": { + "payload": { + "$ref": "#/components/schemas/contact_list_item" + } + } + }, + "contact_conversation_message": { + "type": "object", + "properties": { + "id": { + "type": "integer", + "description": "ID of the message" + }, + "content": { + "type": "string", + "description": "Content of the message" + }, + "account_id": { + "type": "integer", + "description": "ID of the account" + }, + "inbox_id": { + "type": "integer", + "description": "ID of the inbox" + }, + "conversation_id": { + "type": "integer", + "description": "ID of the conversation" + }, + "message_type": { + "type": "integer", + "description": "Type of the message" + }, + "created_at": { + "type": "integer", + "description": "Timestamp when message was created" + }, + "updated_at": { + "type": "string", + "description": "Formatted datetime when message was updated" + }, + "private": { + "type": "boolean", + "description": "Whether the message is private" + }, + "status": { + "type": "string", + "description": "Status of the message" + }, + "source_id": { + "type": "string", + "description": "Source ID of the message", + "nullable": true + }, + "content_type": { + "type": "string", + "description": "Type of the content" + }, + "content_attributes": { + "type": "object", + "description": "Attributes of the content" + }, + "sender_type": { + "type": "string", + "description": "Type of the sender", + "nullable": true + }, + "sender_id": { + "type": "integer", + "description": "ID of the sender", + "nullable": true + }, + "external_source_ids": { + "type": "object", + "description": "External source IDs" + }, + "additional_attributes": { + "type": "object", + "description": "Additional attributes of the message" + }, + "processed_message_content": { + "type": "string", + "description": "Processed message content", + "nullable": true + }, + "sentiment": { + "type": "object", + "description": "Sentiment analysis of the message" + }, + "conversation": { + "type": "object", + "description": "Conversation details", + "properties": { + "assignee_id": { + "type": "integer", + "description": "ID of the assignee", + "nullable": true + }, + "unread_count": { + "type": "integer", + "description": "Count of unread messages" + }, + "last_activity_at": { + "type": "integer", + "description": "Timestamp of last activity" + }, + "contact_inbox": { + "type": "object", + "description": "Contact inbox details", + "properties": { + "source_id": { + "type": "string", + "description": "Source ID of the contact inbox" + } + } + } + } + }, + "sender": { + "type": "object", + "description": "Details of the sender", + "properties": { + "id": { + "type": "integer", + "description": "ID of the sender" + }, + "name": { + "type": "string", + "description": "Name of the sender" + }, + "available_name": { + "type": "string", + "description": "Available name of the sender" + }, + "avatar_url": { + "type": "string", + "description": "URL of the sender's avatar" + }, + "type": { + "type": "string", + "description": "Type of the sender" + }, + "availability_status": { + "type": "string", + "description": "Availability status of the sender" + }, + "thumbnail": { + "type": "string", + "description": "Thumbnail URL of the sender" + } + } + } + } + }, + "contact_conversations_response": { + "type": "object", + "properties": { + "payload": { + "type": "array", + "items": { + "allOf": [ + { + "$ref": "#/components/schemas/conversation" + }, + { + "type": "object", + "properties": { + "meta": { + "type": "object", + "properties": { + "sender": { + "type": "object", + "properties": { + "additional_attributes": { + "type": "object", + "description": "The additional attributes of the sender" + }, + "availability_status": { + "type": "string", + "description": "The availability status of the sender" + }, + "email": { + "type": "string", + "description": "The email of the sender" + }, + "id": { + "type": "number", + "description": "ID fo the sender" + }, + "name": { + "type": "string", + "description": "The name of the sender" + }, + "phone_number": { + "type": "string", + "description": "The phone number of the sender" + }, + "blocked": { + "type": "boolean", + "description": "Whether the sender is blocked" + }, + "identifier": { + "type": "string", + "description": "The identifier of the sender" + }, + "thumbnail": { + "type": "string", + "description": "Avatar URL of the contact" + }, + "custom_attributes": { + "type": "object", + "description": "The custom attributes of the sender" + }, + "last_activity_at": { + "type": "number", + "description": "The last activity at of the sender" + }, + "created_at": { + "type": "number", + "description": "The created at of the sender" + } + } + }, + "channel": { + "type": "string", + "description": "Channel Type" + }, + "assignee": { + "$ref": "#/components/schemas/user" + }, + "hmac_verified": { + "type": "boolean", + "description": "Whether the hmac is verified" + } + } + } + } + } + ] + }, + "description": "List of conversations for the contact" + } + } + }, + "contactable_inboxes_response": { + "type": "object", + "properties": { + "payload": { + "type": "array", + "items": { + "$ref": "#/components/schemas/contact_inbox" + }, + "description": "List of contactable inboxes for the contact" + } + } + } + }, + "parameters": { + "account_id": { + "in": "path", + "name": "account_id", + "schema": { + "type": "integer" + }, + "required": true, + "description": "The numeric ID of the account" + }, + "agent_bot_id": { + "in": "path", + "name": "id", + "schema": { + "type": "integer" + }, + "required": true, + "description": "The ID of the agentbot to be updated" + }, + "team_id": { + "in": "path", + "name": "team_id", + "schema": { + "type": "integer" + }, + "required": true, + "description": "The ID of the team to be updated" + }, + "inbox_id": { + "in": "path", + "name": "inbox_id", + "schema": { + "type": "integer" + }, + "required": true, + "description": "The ID of the Inbox" + }, + "hook_id": { + "in": "path", + "name": "hook_id", + "schema": { + "type": "integer" + }, + "required": true, + "description": "The numeric ID of the integration hook" + }, + "source_id": { + "in": "path", + "name": "source_id", + "required": true, + "schema": { + "type": "string" + }, + "description": "Id of the session for which the conversation is created.\n\n\n\n Source Ids can be obtained through contactable inboxes API or via generated.Website: Chatwoot generated string which can be obtained from webhook events. Phone Channels(Twilio): Phone number in e164 format Email Channels: Contact Email address API Channel: Any Random String" + }, + "contact_sort_param": { + "in": "query", + "name": "sort", + "schema": { + "type": "string", + "enum": [ + "name", + "email", + "phone_number", + "last_activity_at", + "-name", + "-email", + "-phone_number", + "-last_activity_at" + ] + }, + "required": false, + "description": "The attribute by which list should be sorted" + }, + "conversation_id": { + "in": "path", + "name": "conversation_id", + "schema": { + "type": "integer" + }, + "required": true, + "description": "The numeric ID of the conversation" + }, + "conversation_uuid": { + "in": "path", + "name": "conversation_uuid", + "schema": { + "type": "integer" + }, + "required": true, + "description": "The uuid of the conversation" + }, + "custom_filter_id": { + "in": "path", + "name": "custom_filter_id", + "schema": { + "type": "integer" + }, + "required": true, + "description": "The numeric ID of the custom filter" + }, + "webhook_id": { + "in": "path", + "name": "webhook_id", + "schema": { + "type": "integer" + }, + "required": true, + "description": "The numeric ID of the webhook" + }, + "message_id": { + "in": "path", + "name": "message_id", + "schema": { + "type": "integer" + }, + "required": true, + "description": "The numeric ID of the message" + }, + "page": { + "in": "query", + "name": "page", + "schema": { + "type": "integer", + "default": 1 + }, + "required": false, + "description": "The page parameter" + }, + "platform_user_id": { + "in": "path", + "name": "id", + "schema": { + "type": "integer" + }, + "required": true, + "description": "The numeric ID of the user on the platform" + }, + "report_type": { + "in": "query", + "name": "type", + "schema": { + "type": "string", + "enum": [ + "account", + "agent", + "inbox", + "label", + "team" + ] + }, + "required": true, + "description": "Type of report" + }, + "report_metric": { + "in": "query", + "name": "metric", + "schema": { + "type": "string", + "enum": [ + "conversations_count", + "incoming_messages_count", + "outgoing_messages_count", + "avg_first_response_time", + "avg_resolution_time", + "resolutions_count" + ] + }, + "required": true, + "description": "The type of metric" + }, + "public_inbox_identifier": { + "in": "path", + "name": "inbox_identifier", + "schema": { + "type": "string" + }, + "required": true, + "description": "The identifier obtained from API inbox channel" + }, + "public_contact_identifier": { + "in": "path", + "name": "contact_identifier", + "schema": { + "type": "string" + }, + "required": true, + "description": "The source id of contact obtained on contact create" + }, + "portal_id": { + "in": "path", + "name": "id", + "schema": { + "type": "string" + }, + "required": true, + "description": "The slug identifier of the portal" + } + }, + "securitySchemes": { + "userApiKey": { + "type": "apiKey", + "in": "header", + "name": "api_access_token", + "description": "This token can be obtained by visiting the profile page or via rails console. Provides access to endpoints based on the user permissions levels. This token can be saved by an external system when user is created via API, to perform activities on behalf of the user." + }, + "agentBotApiKey": { + "type": "apiKey", + "in": "header", + "name": "api_access_token", + "description": "This token should be provided by system admin or obtained via rails console. This token can be used to build bot integrations and can only access limited apis." + }, + "platformAppApiKey": { + "type": "apiKey", + "in": "header", + "name": "api_access_token", + "description": "This token can be obtained by the system admin after creating a platformApp. This token should be used to provision agent bots, accounts, users and their roles." + } + } + }, + "tags": [ + { + "name": "Contacts API", + "description": "Public contact APIs" + }, + { + "name": "Conversations API", + "description": "Public conversation APIs" + }, + { + "name": "Messages API", + "description": "Public message APIs" + } + ], + "x-tagGroups": [ + { + "name": "Platform", + "tags": [ + "Accounts", + "Account Users", + "AgentBots", + "Users" + ] + }, + { + "name": "Application", + "tags": [ + "Account AgentBots", + "Agents", + "Canned Responses", + "Contacts", + "Contact Labels", + "Conversation Assignments", + "Conversation Labels", + "Conversations", + "Custom Attributes", + "Custom Filters", + "Inboxes", + "Integrations", + "Messages", + "Profile", + "Reports", + "Teams", + "Webhooks", + "Automation Rule", + "Help Center" + ] + }, + { + "name": "Client", + "tags": [ + "Contacts API", + "Conversations API", + "Messages API" + ] + }, + { + "name": "Others", + "tags": [ + "CSAT Survey Page" + ] + } + ] +} \ No newline at end of file diff --git a/swagger/tag_groups/other_swagger.json b/swagger/tag_groups/other_swagger.json new file mode 100644 index 000000000..fc61e8721 --- /dev/null +++ b/swagger/tag_groups/other_swagger.json @@ -0,0 +1,4370 @@ +{ + "openapi": "3.0.4", + "info": { + "title": "Chatwoot", + "description": "This is the API documentation for Chatwoot server.", + "version": "1.1.0", + "termsOfService": "https://www.chatwoot.com/terms-of-service/", + "contact": { + "email": "hello@chatwoot.com" + }, + "license": { + "name": "MIT License", + "url": "https://opensource.org/licenses/MIT" + } + }, + "servers": [ + { + "url": "https://app.chatwoot.com/" + } + ], + "paths": { + "/survey/responses/{conversation_uuid}": { + "parameters": [ + { + "$ref": "#/components/parameters/conversation_uuid" + } + ], + "get": { + "tags": [ + "CSAT Survey Page" + ], + "operationId": "get-csat-survey-page", + "summary": "Get CSAT survey page", + "description": "You can redirect the client to this URL, instead of implementing the CSAT survey component yourself.", + "security": [], + "responses": { + "200": { + "description": "Success" + } + } + } + } + }, + "components": { + "schemas": { + "bad_request_error": { + "title": "data", + "type": "object", + "properties": { + "description": { + "type": "string" + }, + "errors": { + "type": "array", + "items": { + "$ref": "#/components/schemas/request_error" + } + } + } + }, + "request_error": { + "type": "object", + "properties": { + "field": { + "type": "string" + }, + "message": { + "type": "string" + }, + "code": { + "type": "string" + } + } + }, + "generic_id": { + "type": "object", + "properties": { + "id": { + "type": "number" + } + } + }, + "canned_response": { + "type": "object", + "properties": { + "id": { + "type": "integer", + "description": "ID of the canned response" + }, + "account_id": { + "type": "integer", + "description": "Account Id" + }, + "short_code": { + "type": "string", + "description": "Short Code for quick access of the canned response" + }, + "content": { + "type": "string", + "description": "Message content for canned response" + }, + "created_at": { + "type": "string", + "description": "The date and time when the canned response was created" + }, + "updated_at": { + "type": "string", + "description": "The date and time when the canned response was updated" + } + } + }, + "custom_attribute": { + "type": "object", + "properties": { + "id": { + "type": "integer", + "description": "Identifier" + }, + "attribute_display_name": { + "type": "string", + "description": "Attribute display name" + }, + "attribute_display_type": { + "type": "string", + "description": "Attribute display type (text, number, currency, percent, link, date, list, checkbox)" + }, + "attribute_description": { + "type": "string", + "description": "Attribute description" + }, + "attribute_key": { + "type": "string", + "description": "Attribute unique key value" + }, + "regex_pattern": { + "type": "string", + "description": "Regex pattern" + }, + "regex_cue": { + "type": "string", + "description": "Regex cue" + }, + "attribute_values": { + "type": "string", + "description": "Attribute values" + }, + "attribute_model": { + "type": "string", + "description": "Attribute type(conversation_attribute/contact_attribute)" + }, + "default_value": { + "type": "string", + "description": "Attribute default value" + }, + "created_at": { + "type": "string", + "description": "The date and time when the custom attribute was created" + }, + "updated_at": { + "type": "string", + "description": "The date and time when the custom attribute was updated" + } + } + }, + "automation_rule": { + "type": "object", + "properties": { + "payload": { + "description": "Response payload that contains automation rule(s)", + "oneOf": [ + { + "type": "array", + "description": "Array of automation rules (for listing endpoint)", + "items": { + "$ref": "#/components/schemas/automation_rule_item" + } + }, + { + "type": "object", + "description": "Single automation rule (for show/create/update endpoints)", + "allOf": [ + { + "$ref": "#/components/schemas/automation_rule_item" + } + ] + } + ] + } + } + }, + "automation_rule_item": { + "type": "object", + "properties": { + "id": { + "type": "integer", + "description": "The ID of the automation rule" + }, + "account_id": { + "type": "integer", + "description": "Account Id" + }, + "name": { + "type": "string", + "description": "The name of the rule", + "example": "Add label on message create event" + }, + "description": { + "type": "string", + "description": "Description to give more context about the rule", + "example": "Add label support and sales on message create event if incoming message content contains text help" + }, + "event_name": { + "type": "string", + "description": "Automation Rule event, on which we call the actions(conversation_created, conversation_updated, message_created)", + "enum": [ + "conversation_created", + "conversation_updated", + "message_created" + ], + "example": "message_created" + }, + "conditions": { + "type": "array", + "description": "Array of conditions on which conversation/message filter would work", + "items": { + "type": "object", + "properties": { + "values": { + "type": "array", + "items": { + "type": "string" + } + }, + "attribute_key": { + "type": "string" + }, + "query_operator": { + "type": "string" + }, + "filter_operator": { + "type": "string" + } + }, + "example": { + "attribute_key": "content", + "filter_operator": "contains", + "values": [ + "help" + ], + "query_operator": "and" + } + } + }, + "actions": { + "type": "array", + "description": "Array of actions which we perform when condition matches", + "items": { + "type": "object", + "properties": { + "action_name": { + "type": "string" + }, + "action_params": { + "type": "array", + "items": { + "type": "string" + } + } + }, + "example": { + "action_name": "add_label", + "action_params": [ + "support", + "sales" + ] + } + } + }, + "created_on": { + "type": "integer", + "description": "The timestamp when the rule was created" + }, + "active": { + "type": "boolean", + "description": "Enable/disable automation rule" + } + } + }, + "portal": { + "type": "object", + "properties": { + "payload": { + "type": "array", + "items": { + "$ref": "#/components/schemas/portal_item" + } + } + } + }, + "portal_single": { + "type": "object", + "properties": { + "payload": { + "$ref": "#/components/schemas/portal_item" + } + } + }, + "portal_config": { + "type": "object", + "description": "Configuration settings for the portal", + "properties": { + "allowed_locales": { + "type": "array", + "description": "List of allowed locales for the portal", + "items": { + "type": "object", + "properties": { + "code": { + "type": "string", + "description": "The language code" + }, + "articles_count": { + "type": "integer", + "description": "Number of articles in this locale" + }, + "categories_count": { + "type": "integer", + "description": "Number of categories in this locale" + } + } + } + } + } + }, + "portal_logo": { + "type": "object", + "properties": { + "id": { + "type": "integer", + "description": "ID of the logo file" + }, + "portal_id": { + "type": "integer", + "description": "ID of the portal this logo belongs to" + }, + "file_type": { + "type": "string", + "description": "MIME type of the file" + }, + "account_id": { + "type": "integer", + "description": "ID of the account" + }, + "file_url": { + "type": "string", + "description": "URL to access the logo file" + }, + "blob_id": { + "type": "integer", + "description": "ID of the blob" + }, + "filename": { + "type": "string", + "description": "Name of the file" + } + } + }, + "portal_meta": { + "type": "object", + "properties": { + "all_articles_count": { + "type": "integer", + "description": "Total number of articles" + }, + "archived_articles_count": { + "type": "integer", + "nullable": true, + "description": "Number of archived articles" + }, + "published_count": { + "type": "integer", + "nullable": true, + "description": "Number of published articles" + }, + "draft_articles_count": { + "type": "integer", + "nullable": true, + "description": "Number of draft articles" + }, + "categories_count": { + "type": "integer", + "description": "Number of categories" + }, + "default_locale": { + "type": "string", + "description": "Default locale for the portal" + } + } + }, + "portal_item": { + "type": "object", + "properties": { + "id": { + "type": "integer", + "description": "The ID of the portal" + }, + "archived": { + "type": "boolean", + "description": "Whether the portal is archived" + }, + "color": { + "type": "string", + "description": "The color code for the portal" + }, + "config": { + "$ref": "#/components/schemas/portal_config" + }, + "custom_domain": { + "type": "string", + "description": "Custom domain for the portal" + }, + "header_text": { + "type": "string", + "description": "The header text for the portal" + }, + "homepage_link": { + "type": "string", + "description": "Homepage link for the portal" + }, + "name": { + "type": "string", + "description": "Name of the portal" + }, + "slug": { + "type": "string", + "description": "URL slug for the portal" + }, + "page_title": { + "type": "string", + "description": "Page title for the portal" + }, + "account_id": { + "type": "integer", + "description": "ID of the account the portal belongs to" + }, + "inbox": { + "$ref": "#/components/schemas/inbox" + }, + "logo": { + "$ref": "#/components/schemas/portal_logo" + }, + "meta": { + "$ref": "#/components/schemas/portal_meta" + } + } + }, + "category": { + "type": "object", + "properties": { + "id": { + "type": "integer" + }, + "description": { + "type": "string", + "description": "The text content." + }, + "locale": { + "type": "string" + }, + "name": { + "type": "string" + }, + "slug": { + "type": "string" + }, + "position": { + "type": "integer" + }, + "portal_id": { + "type": "integer" + }, + "account_id": { + "type": "integer" + }, + "associated_category_id": { + "type": "integer", + "description": "To associate similar categories to each other, e.g same category of product documentation in different languages" + }, + "parent_category_id": { + "type": "integer", + "description": "To define parent category, e.g product documentation has multiple level features in sales category or in engineering category." + } + } + }, + "article": { + "type": "object", + "properties": { + "id": { + "type": "integer" + }, + "content": { + "type": "string", + "description": "The text content." + }, + "meta": { + "type": "object" + }, + "position": { + "type": "integer" + }, + "status": { + "type": "integer", + "enum": [ + "draft", + "published", + "archived" + ] + }, + "title": { + "type": "string" + }, + "slug": { + "type": "string" + }, + "views": { + "type": "integer" + }, + "portal_id": { + "type": "integer" + }, + "account_id": { + "type": "integer" + }, + "author_id": { + "type": "integer" + }, + "category_id": { + "type": "integer" + }, + "folder_id": { + "type": "integer" + }, + "associated_article_id": { + "type": "integer", + "description": "To associate similar articles to each other, e.g to provide the link for the reference." + } + } + }, + "contact": { + "type": "object", + "properties": { + "payload": { + "type": "array", + "items": { + "type": "object", + "properties": { + "additional_attributes": { + "type": "object", + "description": "The object containing additional attributes related to the contact" + }, + "availability_status": { + "type": "string", + "description": "The availability status of the contact" + }, + "email": { + "type": "string", + "description": "The email address of the contact" + }, + "id": { + "type": "integer", + "description": "The ID of the contact" + }, + "name": { + "type": "string", + "description": "The name of the contact" + }, + "phone_number": { + "type": "string", + "description": "The phone number of the contact" + }, + "blocked": { + "type": "boolean", + "description": "Whether the contact is blocked" + }, + "identifier": { + "type": "string", + "description": "The identifier of the contact" + }, + "thumbnail": { + "type": "string", + "description": "The thumbnail of the contact" + }, + "custom_attributes": { + "type": "object", + "description": "The custom attributes of the contact", + "example": { + "attribute_key": "attribute_value", + "signed_up_at": "dd/mm/yyyy" + } + }, + "last_activity_at": { + "type": "integer", + "description": "The last activity at of the contact" + }, + "created_at": { + "type": "integer", + "description": "The created at of the contact" + }, + "contact_inboxes": { + "type": "array", + "items": { + "$ref": "#/components/schemas/contact_inboxes" + } + } + } + } + } + } + }, + "conversation": { + "type": "object", + "properties": { + "id": { + "type": "number", + "description": "ID of the conversation" + }, + "messages": { + "type": "array", + "items": { + "$ref": "#/components/schemas/message" + } + }, + "account_id": { + "type": "number", + "description": "Account Id" + }, + "uuid": { + "type": "string", + "description": "UUID of the conversation" + }, + "additional_attributes": { + "type": "object", + "description": "The object containing additional attributes related to the conversation" + }, + "agent_last_seen_at": { + "type": "number", + "description": "The last activity at of the agent" + }, + "assignee_last_seen_at": { + "type": "number", + "description": "The last activity at of the assignee" + }, + "can_reply": { + "type": "boolean", + "description": "Whether the conversation can be replied to" + }, + "contact_last_seen_at": { + "type": "number", + "description": "The last activity at of the contact" + }, + "custom_attributes": { + "type": "object", + "description": "The object to save custom attributes for conversation, accepts custom attributes key and value" + }, + "inbox_id": { + "type": "number", + "description": "ID of the inbox" + }, + "labels": { + "type": "array", + "items": { + "type": "string" + }, + "description": "The labels of the conversation" + }, + "muted": { + "type": "boolean", + "description": "Whether the conversation is muted" + }, + "snoozed_until": { + "type": "number", + "description": "The time at which the conversation will be unmuted" + }, + "status": { + "type": "string", + "enum": [ + "open", + "resolved", + "pending" + ], + "description": "The status of the conversation" + }, + "created_at": { + "type": "number", + "description": "The time at which conversation was created" + }, + "updated_at": { + "type": "number", + "description": "The time at which conversation was updated" + }, + "timestamp": { + "type": "string", + "description": "The time at which conversation was created" + }, + "first_reply_created_at": { + "type": "number", + "description": "The time at which the first reply was created" + }, + "unread_count": { + "type": "number", + "description": "The number of unread messages" + }, + "last_non_activity_message": { + "$ref": "#/components/schemas/message" + }, + "last_activity_at": { + "type": "number", + "description": "The last activity at of the conversation" + }, + "priority": { + "type": "string", + "description": "The priority of the conversation" + }, + "waiting_since": { + "type": "number", + "description": "The time at which the conversation was waiting" + }, + "sla_policy_id": { + "type": "number", + "description": "The ID of the SLA policy" + }, + "applied_sla": { + "type": "object", + "description": "The applied SLA" + }, + "sla_events": { + "type": "array", + "items": { + "type": "object", + "description": "SLA event objects" + } + } + } + }, + "message": { + "type": "object", + "properties": { + "id": { + "type": "number", + "description": "The ID of the message" + }, + "content": { + "type": "string", + "description": "The text content of the message" + }, + "account_id": { + "type": "number", + "description": "The ID of the account" + }, + "inbox_id": { + "type": "number", + "description": "The ID of the inbox" + }, + "conversation_id": { + "type": "number", + "description": "The ID of the conversation" + }, + "message_type": { + "type": "integer", + "enum": [ + 0, + 1, + 2 + ], + "description": "The type of the message" + }, + "created_at": { + "type": "integer", + "description": "The time at which message was created" + }, + "updated_at": { + "type": "integer", + "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" + ], + "description": "The status of the message" + }, + "source_id": { + "type": "string", + "description": "The source ID of the message" + }, + "content_type": { + "type": "string", + "enum": [ + "text", + "input_select", + "cards", + "form" + ], + "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" + ], + "description": "The type of the sender" + }, + "sender_id": { + "type": "number", + "description": "The ID of the sender" + }, + "external_source_ids": { + "type": "object", + "description": "The external source IDs of the message" + }, + "additional_attributes": { + "type": "object", + "description": "The additional attributes of the message" + }, + "processed_message_content": { + "type": "string", + "description": "The processed message content" + }, + "sentiment": { + "type": "object", + "description": "The sentiment of the message" + }, + "conversation": { + "type": "object", + "description": "The conversation object" + }, + "attachment": { + "type": "object", + "description": "The file object attached to the image" + }, + "sender": { + "type": "object", + "description": "User/Agent/AgentBot object" + } + } + }, + "user": { + "type": "object", + "properties": { + "id": { + "type": "number" + }, + "access_token": { + "type": "string" + }, + "account_id": { + "type": "number" + }, + "available_name": { + "type": "string" + }, + "avatar_url": { + "type": "string" + }, + "confirmed": { + "type": "boolean" + }, + "display_name": { + "type": "string", + "nullable": true + }, + "message_signature": { + "type": "string", + "nullable": true + }, + "email": { + "type": "string" + }, + "hmac_identifier": { + "type": "string" + }, + "inviter_id": { + "type": "number" + }, + "name": { + "type": "string" + }, + "provider": { + "type": "string" + }, + "pubsub_token": { + "type": "string" + }, + "role": { + "type": "string", + "enum": [ + "agent", + "administrator" + ] + }, + "ui_settings": { + "type": "object" + }, + "uid": { + "type": "string" + }, + "type": { + "type": "string", + "nullable": true + }, + "custom_attributes": { + "type": "object", + "description": "Available for users who are created through platform APIs and has custom attributes associated." + }, + "accounts": { + "type": "array", + "items": { + "type": "object", + "properties": { + "id": { + "type": "number" + }, + "name": { + "type": "string" + }, + "status": { + "type": "string" + }, + "active_at": { + "type": "string", + "format": "date-time" + }, + "role": { + "type": "string", + "enum": [ + "administrator", + "agent" + ] + }, + "permissions": { + "type": "array", + "items": { + "type": "string" + } + }, + "availability": { + "type": "string" + }, + "availability_status": { + "type": "string" + }, + "auto_offline": { + "type": "boolean" + }, + "custom_role_id": { + "type": "number", + "nullable": true + }, + "custom_role": { + "type": "object", + "nullable": true + } + } + } + } + } + }, + "agent": { + "type": "object", + "properties": { + "id": { + "type": "integer" + }, + "account_id": { + "type": "integer" + }, + "availability_status": { + "type": "string", + "enum": [ + "available", + "busy", + "offline" + ], + "description": "The availability status of the agent computed by Chatwoot." + }, + "auto_offline": { + "type": "boolean", + "description": "Whether the availability status of agent is configured to go offline automatically when away." + }, + "confirmed": { + "type": "boolean", + "description": "Whether the agent has confirmed their email address." + }, + "email": { + "type": "string", + "description": "The email of the agent" + }, + "available_name": { + "type": "string", + "description": "The available name of the agent" + }, + "name": { + "type": "string", + "description": "The name of the agent" + }, + "role": { + "type": "string", + "enum": [ + "agent", + "administrator" + ], + "description": "The role of the agent" + }, + "thumbnail": { + "type": "string", + "description": "The thumbnail of the agent" + }, + "custom_role_id": { + "type": "integer", + "description": "The custom role id of the agent" + } + } + }, + "inbox": { + "type": "object", + "properties": { + "id": { + "type": "number", + "description": "ID of the inbox" + }, + "name": { + "type": "string", + "description": "The name of the inbox" + }, + "website_url": { + "type": "string", + "description": "Website URL" + }, + "channel_type": { + "type": "string", + "description": "The type of the inbox" + }, + "avatar_url": { + "type": "string", + "description": "The avatar image of the inbox" + }, + "widget_color": { + "type": "string", + "description": "Widget Color used for customization of the widget" + }, + "website_token": { + "type": "string", + "description": "Website Token" + }, + "enable_auto_assignment": { + "type": "boolean", + "description": "The flag which shows whether Auto Assignment is enabled or not" + }, + "web_widget_script": { + "type": "string", + "description": "Script used to load the website widget" + }, + "welcome_title": { + "type": "string", + "description": "Welcome title to be displayed on the widget" + }, + "welcome_tagline": { + "type": "string", + "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", + "description": "A greeting message when the user starts the conversation" + }, + "channel_id": { + "type": "number", + "description": "ID of the channel this inbox belongs to" + }, + "working_hours_enabled": { + "type": "boolean", + "description": "The flag which shows whether working hours feature is enabled" + }, + "enable_email_collect": { + "type": "boolean", + "description": "The flag to enable collecting email from contacts" + }, + "csat_survey_enabled": { + "type": "boolean", + "description": "The flag to enable CSAT survey" + }, + "auto_assignment_config": { + "type": "object", + "description": "Configuration settings for auto assignment" + }, + "out_of_office_message": { + "type": "string", + "description": "Message to show when agents are out of office" + }, + "working_hours": { + "type": "array", + "description": "Configuration for working hours of the inbox", + "items": { + "type": "object", + "properties": { + "day_of_week": { + "type": "number", + "description": "Day of the week (0-6, where 0 is Sunday)" + }, + "closed_all_day": { + "type": "boolean", + "description": "Whether the inbox is closed for the entire day" + }, + "open_hour": { + "type": "number", + "description": "Hour when inbox opens (0-23)" + }, + "open_minutes": { + "type": "number", + "description": "Minutes of the hour when inbox opens (0-59)" + }, + "close_hour": { + "type": "number", + "description": "Hour when inbox closes (0-23)" + }, + "close_minutes": { + "type": "number", + "description": "Minutes of the hour when inbox closes (0-59)" + }, + "open_all_day": { + "type": "boolean", + "description": "Whether the inbox is open for the entire day" + } + } + } + }, + "timezone": { + "type": "string", + "description": "Timezone configuration for the inbox" + }, + "callback_webhook_url": { + "type": "string", + "description": "Webhook URL for callbacks" + }, + "allow_messages_after_resolved": { + "type": "boolean", + "description": "Whether to allow messages after a conversation is resolved" + }, + "lock_to_single_conversation": { + "type": "boolean", + "description": "Whether to lock a contact to a single conversation" + }, + "sender_name_type": { + "type": "string", + "description": "Type of sender name to display (e.g., friendly)" + }, + "business_name": { + "type": "string", + "description": "Business name associated with the inbox" + }, + "hmac_mandatory": { + "type": "boolean", + "description": "Whether HMAC verification is mandatory" + }, + "selected_feature_flags": { + "type": "object", + "description": "Selected feature flags for the inbox" + }, + "reply_time": { + "type": "string", + "description": "Expected reply time" + }, + "messaging_service_sid": { + "type": "string", + "description": "Messaging service SID for SMS providers" + }, + "phone_number": { + "type": "string", + "description": "Phone number associated with the inbox" + }, + "medium": { + "type": "string", + "description": "Medium of communication (e.g., sms, email)" + }, + "provider": { + "type": "string", + "description": "Provider of the channel" + } + } + }, + "inbox_contact": { + "type": "object", + "properties": { + "id": { + "type": "number", + "description": "ID of the inbox" + }, + "avatar_url": { + "type": "string", + "description": "The avatar image of the inbox" + }, + "channel_id": { + "type": "number", + "description": "The ID of the channel" + }, + "name": { + "type": "string", + "description": "The name of the inbox" + }, + "channel_type": { + "type": "string", + "description": "The type of the inbox" + }, + "provider": { + "type": "string", + "description": "The provider of the inbox" + } + } + }, + "agent_bot": { + "type": "object", + "properties": { + "id": { + "type": "number", + "description": "ID of the agent bot" + }, + "name": { + "type": "string", + "description": "The name of the agent bot" + }, + "description": { + "type": "string", + "description": "The description about the agent bot" + }, + "thumbnail": { + "type": "string", + "description": "The thumbnail of the agent bot" + }, + "outgoing_url": { + "type": "string", + "description": "The webhook URL for the bot" + }, + "bot_type": { + "type": "string", + "description": "The type of the bot" + }, + "bot_config": { + "type": "object", + "description": "The configuration of the bot" + }, + "account_id": { + "type": "number", + "description": "Account ID if it's an account specific bot" + }, + "access_token": { + "type": "string", + "description": "The access token for the bot" + }, + "system_bot": { + "type": "boolean", + "description": "Whether the bot is a system bot" + } + } + }, + "contact_inboxes": { + "type": "object", + "properties": { + "source_id": { + "type": "string", + "description": "Contact Inbox Source Id" + }, + "inbox": { + "$ref": "#/components/schemas/inbox_contact" + } + } + }, + "contactable_inboxes": { + "type": "object", + "properties": { + "source_id": { + "type": "string", + "description": "Contact Inbox Source Id" + }, + "inbox": { + "$ref": "#/components/schemas/inbox" + } + } + }, + "custom_filter": { + "type": "object", + "properties": { + "id": { + "type": "number", + "description": "The ID of the custom filter" + }, + "name": { + "type": "string", + "description": "The name of the custom filter" + }, + "type": { + "type": "string", + "enum": [ + "conversation", + "contact", + "report" + ], + "description": "The description about the custom filter" + }, + "query": { + "type": "object", + "description": "A query that needs to be saved as a custom filter" + }, + "created_at": { + "type": "string", + "format": "date-time", + "description": "The time at which the custom filter was created" + }, + "updated_at": { + "type": "string", + "format": "date-time", + "description": "The time at which the custom filter was updated" + } + } + }, + "webhook": { + "type": "object", + "properties": { + "id": { + "type": "number", + "description": "The ID of the webhook" + }, + "url": { + "type": "string", + "description": "The url to which the events will be send" + }, + "subscriptions": { + "type": "array", + "items": { + "type": "string", + "enum": [ + "conversation_created", + "conversation_status_changed", + "conversation_updated", + "contact_created", + "contact_updated", + "message_created", + "message_updated", + "webwidget_triggered" + ] + }, + "description": "The list of subscribed events" + }, + "account_id": { + "type": "number", + "description": "The id of the account which the webhook object belongs to" + } + } + }, + "account": { + "type": "object", + "properties": { + "id": { + "type": "number", + "description": "Account ID" + }, + "name": { + "type": "string", + "description": "Name of the account" + }, + "role": { + "type": "string", + "enum": [ + "administrator", + "agent" + ], + "description": "The user role in the account" + } + } + }, + "account_user": { + "type": "array", + "description": "Array of account users", + "items": { + "type": "object", + "properties": { + "account_id": { + "type": "integer", + "description": "The ID of the account" + }, + "user_id": { + "type": "integer", + "description": "The ID of the user" + }, + "role": { + "type": "string", + "description": "whether user is an administrator or agent" + } + } + } + }, + "platform_account": { + "type": "object", + "properties": { + "id": { + "type": "number", + "description": "Account ID" + }, + "name": { + "type": "string", + "description": "Name of the account" + } + } + }, + "team": { + "type": "object", + "properties": { + "id": { + "type": "number", + "description": "The ID of the team" + }, + "name": { + "type": "string", + "description": "The name of the team" + }, + "description": { + "type": "string", + "description": "The description about the team" + }, + "allow_auto_assign": { + "type": "boolean", + "description": "If this setting is turned on, the system would automatically assign the conversation to an agent in the team while assigning the conversation to a team" + }, + "account_id": { + "type": "number", + "description": "The ID of the account with the team is a part of" + }, + "is_member": { + "type": "boolean", + "description": "This field shows whether the current user is a part of the team" + } + } + }, + "integrations_app": { + "type": "object", + "properties": { + "id": { + "type": "string", + "description": "The ID of the integration" + }, + "name": { + "type": "string", + "description": "The name of the integration" + }, + "description": { + "type": "string", + "description": "The description about the team" + }, + "hook_type": { + "type": "string", + "description": "Whether the integration is an account or inbox integration" + }, + "enabled": { + "type": "boolean", + "description": "Whether the integration is enabled for the account" + }, + "allow_multiple_hooks": { + "type": "boolean", + "description": "Whether multiple hooks can be created for the integration" + }, + "hooks": { + "type": "array", + "items": { + "type": "object" + }, + "description": "If there are any hooks created for this integration" + } + } + }, + "integrations_hook": { + "type": "object", + "properties": { + "id": { + "type": "string", + "description": "The ID of the integration hook" + }, + "app_id": { + "type": "string", + "description": "The ID of the integration app" + }, + "inbox_id": { + "type": "string", + "description": "Inbox ID if its an Inbox integration" + }, + "account_id": { + "type": "string", + "description": "Account ID of the integration" + }, + "status": { + "type": "boolean", + "description": "Whether the integration hook is enabled for the account" + }, + "hook_type": { + "type": "boolean", + "description": "Whether its an account or inbox integration hook" + }, + "settings": { + "type": "object", + "description": "The associated settings for the integration" + } + } + }, + "public_contact": { + "type": "object", + "properties": { + "id": { + "type": "integer", + "description": "Id of the contact" + }, + "source_id": { + "type": "string", + "description": "The session identifier of the contact" + }, + "name": { + "type": "string", + "description": "Name of the contact" + }, + "email": { + "type": "string", + "description": "Email of the contact" + }, + "pubsub_token": { + "type": "string", + "description": "The token to be used to connect to chatwoot websocket" + } + } + }, + "public_conversation": { + "type": "object", + "properties": { + "id": { + "type": "integer", + "description": "Id of the conversation" + }, + "inbox_id": { + "type": "string", + "description": "The inbox id of the conversation" + }, + "messages": { + "type": "array", + "items": { + "$ref": "#/components/schemas/message" + }, + "description": "Messages in the conversation" + }, + "contact": { + "type": "object", + "description": "The contact information associated to the conversation" + } + } + }, + "public_message": { + "type": "object", + "properties": { + "id": { + "type": "string", + "description": "Id of the message" + }, + "content": { + "type": "string", + "description": "Text content of the message" + }, + "message_type": { + "type": "string", + "description": "Denotes the message type" + }, + "content_type": { + "type": "string", + "description": "Content type of the message" + }, + "content_attributes": { + "type": "string", + "description": "Additional content attributes of the message" + }, + "created_at": { + "type": "string", + "description": "Created at time stamp of the message" + }, + "conversation_id": { + "type": "string", + "description": "Conversation Id of the message" + }, + "attachments": { + "type": "array", + "items": { + "type": "object" + }, + "description": "Attachments if any" + }, + "sender": { + "type": "object", + "description": "Details of the sender" + } + } + }, + "public_inbox": { + "type": "object", + "properties": { + "identifier": { + "type": "string", + "description": "Inbox identifier" + }, + "name": { + "type": "string", + "description": "Name of the inbox" + }, + "timezone": { + "type": "string", + "description": "The timezone defined on the inbox" + }, + "working_hours": { + "type": "array", + "description": "The working hours defined on the inbox", + "items": { + "type": "object", + "properties": { + "day_of_week": { + "type": "integer", + "description": "Day of the week as a number. Sunday -> 0, Saturday -> 6" + }, + "open_all_day": { + "type": "boolean", + "description": "Whether or not the business is open the whole day" + }, + "closed_all_day": { + "type": "boolean", + "description": "Whether or not the business is closed the whole day" + }, + "open_hour": { + "type": "integer", + "description": "Opening hour. Can be null if closed all day" + }, + "open_minutes": { + "type": "integer", + "description": "Opening minute. Can be null if closed all day" + }, + "close_hour": { + "type": "integer", + "description": "Closing hour. Can be null if closed all day" + }, + "close_minutes": { + "type": "integer", + "description": "Closing minute. Can be null if closed all day" + } + } + } + }, + "working_hours_enabled": { + "type": "boolean", + "description": "Whether of not the working hours are enabled on the inbox" + }, + "csat_survey_enabled": { + "type": "boolean", + "description": "Whether of not the Customer Satisfaction survey is enabled on the inbox" + }, + "greeting_enabled": { + "type": "boolean", + "description": "Whether of not the Greeting Message is enabled on the inbox" + }, + "identity_validation_enabled": { + "type": "boolean", + "description": "Whether of not the User Identity Validation is enforced on the inbox" + } + } + }, + "account_create_update_payload": { + "type": "object", + "properties": { + "name": { + "type": "string", + "description": "Name of the account", + "example": "My Account" + }, + "locale": { + "type": "string", + "description": "The locale of the account", + "example": "en" + }, + "domain": { + "type": "string", + "description": "The domain of the account", + "example": "example.com" + }, + "support_email": { + "type": "string", + "description": "The support email of the account", + "example": "support@example.com" + }, + "status": { + "type": "string", + "enum": [ + "active", + "suspended" + ], + "description": "The status of the account", + "example": "active" + }, + "limits": { + "type": "object", + "description": "The limits of the account", + "example": {} + }, + "custom_attributes": { + "type": "object", + "description": "The custom attributes of the account", + "example": {} + } + } + }, + "account_user_create_update_payload": { + "type": "object", + "required": [ + "user_id", + "role" + ], + "properties": { + "user_id": { + "type": "integer", + "description": "The ID of the user", + "example": 1 + }, + "role": { + "type": "string", + "description": "whether user is an administrator or agent", + "example": "administrator" + } + } + }, + "platform_agent_bot_create_update_payload": { + "type": "object", + "properties": { + "name": { + "type": "string", + "description": "The name of the agent bot", + "example": "My Agent Bot" + }, + "description": { + "type": "string", + "description": "The description of the agent bot", + "example": "This is a sample agent bot" + }, + "outgoing_url": { + "type": "string", + "description": "The webhook URL for the bot", + "example": "https://example.com/webhook" + }, + "account_id": { + "type": "integer", + "description": "The account ID to associate the agent bot with", + "example": 1 + }, + "avatar": { + "type": "string", + "format": "binary", + "description": "Send the form data with the avatar image binary or use the avatar_url" + }, + "avatar_url": { + "type": "string", + "description": "The url to a jpeg, png file for the agent bot avatar", + "example": "https://example.com/avatar.png" + } + } + }, + "agent_bot_create_update_payload": { + "type": "object", + "properties": { + "name": { + "type": "string", + "description": "The name of the agent bot", + "example": "My Agent Bot" + }, + "description": { + "type": "string", + "description": "The description of the agent bot", + "example": "This is a sample agent bot" + }, + "outgoing_url": { + "type": "string", + "description": "The webhook URL for the bot", + "example": "https://example.com/webhook" + }, + "avatar": { + "type": "string", + "format": "binary", + "description": "Send the form data with the avatar image binary or use the avatar_url" + }, + "avatar_url": { + "type": "string", + "description": "The url to a jpeg, png file for the agent bot avatar", + "example": "https://example.com/avatar.png" + }, + "bot_type": { + "type": "integer", + "description": "The type of the bot (0 for webhook)", + "example": 0 + }, + "bot_config": { + "type": "object", + "description": "The configuration for the bot", + "example": {} + } + } + }, + "user_create_update_payload": { + "type": "object", + "properties": { + "name": { + "type": "string", + "description": "Name of the user", + "example": "Daniel" + }, + "display_name": { + "type": "string", + "description": "Display name of the user", + "example": "Dan" + }, + "email": { + "type": "string", + "description": "Email of the user", + "example": "daniel@acme.inc" + }, + "password": { + "type": "string", + "description": "Password must contain uppercase, lowercase letters, number and a special character", + "example": "Password2!" + }, + "custom_attributes": { + "type": "object", + "description": "Custom attributes you want to associate with the user", + "example": {} + } + } + }, + "canned_response_create_update_payload": { + "type": "object", + "properties": { + "content": { + "type": "string", + "description": "Message content for canned response", + "example": "Hello, {{contact.name}}! Welcome to our service." + }, + "short_code": { + "type": "string", + "description": "Short Code for quick access of the canned response", + "example": "welcome" + } + } + }, + "custom_attribute_create_update_payload": { + "type": "object", + "properties": { + "attribute_display_name": { + "type": "string", + "description": "Attribute display name", + "example": "Custom Attribute" + }, + "attribute_display_type": { + "type": "integer", + "description": "Attribute display type (text- 0, number- 1, currency- 2, percent- 3, link- 4, date- 5, list- 6, checkbox- 7)", + "example": 0 + }, + "attribute_description": { + "type": "string", + "description": "Attribute description", + "example": "This is a custom attribute" + }, + "attribute_key": { + "type": "string", + "description": "Attribute unique key value", + "example": "custom_attribute" + }, + "attribute_values": { + "type": "array", + "description": "Attribute values", + "items": { + "type": "string" + }, + "example": [ + "value1", + "value2" + ] + }, + "attribute_model": { + "type": "integer", + "description": "Attribute type(conversation_attribute- 0, contact_attribute- 1)", + "example": 0 + }, + "regex_pattern": { + "type": "string", + "description": "Regex pattern (Only applicable for type- text). The regex pattern is used to validate the attribute value(s).", + "example": "^[a-zA-Z0-9]+$" + }, + "regex_cue": { + "type": "string", + "description": "Regex cue message (Only applicable for type- text). The cue message is shown when the regex pattern is not matched.", + "example": "Please enter a valid value" + } + } + }, + "agent_create_payload": { + "type": "object", + "required": [ + "name", + "email", + "role" + ], + "properties": { + "name": { + "type": "string", + "description": "Full Name of the agent", + "example": "John Doe" + }, + "email": { + "type": "string", + "description": "Email of the Agent", + "example": "john.doe@acme.inc" + }, + "role": { + "type": "string", + "enum": [ + "agent", + "administrator" + ], + "description": "Whether its administrator or agent", + "example": "agent" + }, + "availability_status": { + "type": "string", + "enum": [ + "available", + "busy", + "offline" + ], + "description": "The availability setting of the agent.", + "example": "available" + }, + "auto_offline": { + "type": "boolean", + "description": "Whether the availability status of agent is configured to go offline automatically when away.", + "example": true + } + } + }, + "agent_update_payload": { + "type": "object", + "required": [ + "role" + ], + "properties": { + "role": { + "type": "string", + "enum": [ + "agent", + "administrator" + ], + "description": "Whether its administrator or agent", + "example": "agent" + }, + "availability_status": { + "type": "string", + "enum": [ + "available", + "busy", + "offline" + ], + "description": "The availability status of the agent.", + "example": "available" + }, + "auto_offline": { + "type": "boolean", + "description": "Whether the availability status of agent is configured to go offline automatically when away.", + "example": true + } + } + }, + "contact_create_payload": { + "type": "object", + "required": [ + "inbox_id" + ], + "properties": { + "inbox_id": { + "type": "number", + "description": "ID of the inbox to which the contact belongs", + "example": 1 + }, + "name": { + "type": "string", + "description": "name of the contact", + "example": "Alice" + }, + "email": { + "type": "string", + "description": "email of the contact", + "example": "alice@acme.inc" + }, + "blocked": { + "type": "boolean", + "description": "whether the contact is blocked or not", + "example": false + }, + "phone_number": { + "type": "string", + "description": "phone number of the contact", + "example": "+123456789" + }, + "avatar": { + "type": "string", + "format": "binary", + "description": "Send the form data with the avatar image binary or use the avatar_url" + }, + "avatar_url": { + "type": "string", + "description": "The url to a jpeg, png file for the contact avatar", + "example": "https://example.com/avatar.png" + }, + "identifier": { + "type": "string", + "description": "A unique identifier for the contact in external system", + "example": "1234567890" + }, + "additional_attributes": { + "type": "object", + "description": "An object where you can store additional attributes for contact. example {\"type\":\"customer\", \"age\":30}", + "example": { + "type": "customer", + "age": 30 + } + }, + "custom_attributes": { + "type": "object", + "description": "An object where you can store custom attributes for contact. example {\"type\":\"customer\", \"age\":30}, this should have a valid custom attribute definition.", + "example": {} + } + } + }, + "contact_update_payload": { + "type": "object", + "properties": { + "name": { + "type": "string", + "description": "name of the contact", + "example": "Alice" + }, + "email": { + "type": "string", + "description": "email of the contact", + "example": "alice@acme.inc" + }, + "blocked": { + "type": "boolean", + "description": "whether the contact is blocked or not", + "example": false + }, + "phone_number": { + "type": "string", + "description": "phone number of the contact", + "example": "+123456789" + }, + "avatar": { + "type": "string", + "format": "binary", + "description": "Send the form data with the avatar image binary or use the avatar_url" + }, + "avatar_url": { + "type": "string", + "description": "The url to a jpeg, png file for the contact avatar", + "example": "https://example.com/avatar.png" + }, + "identifier": { + "type": "string", + "description": "A unique identifier for the contact in external system", + "example": "1234567890" + }, + "additional_attributes": { + "type": "object", + "description": "An object where you can store additional attributes for contact. example {\"type\":\"customer\", \"age\":30}", + "example": { + "type": "customer", + "age": 30 + } + }, + "custom_attributes": { + "type": "object", + "description": "An object where you can store custom attributes for contact. example {\"type\":\"customer\", \"age\":30}, this should have a valid custom attribute definition.", + "example": {} + } + } + }, + "conversation_create_payload": { + "type": "object", + "required": [ + "source_id", + "inbox_id" + ], + "properties": { + "source_id": { + "type": "string", + "description": "Conversation source id", + "example": "1234567890" + }, + "inbox_id": { + "type": "integer", + "description": "Id of inbox in which the conversation is created Allowed Inbox Types: Website, Phone, Api, Email", + "example": 1 + }, + "contact_id": { + "type": "integer", + "description": "Contact Id for which conversation is created", + "example": 1 + }, + "additional_attributes": { + "type": "object", + "description": "Lets you specify attributes like browser information", + "example": { + "browser": "Chrome", + "browser_version": "89.0.4389.82", + "os": "Windows", + "os_version": "10" + } + }, + "custom_attributes": { + "type": "object", + "description": "The object to save custom attributes for conversation, accepts custom attributes key and value", + "example": { + "attribute_key": "attribute_value", + "priority_conversation_number": 3 + } + }, + "status": { + "type": "string", + "enum": [ + "open", + "resolved", + "pending" + ], + "description": "Specify the conversation whether it's pending, open, closed", + "example": "open" + }, + "assignee_id": { + "type": "integer", + "description": "Agent Id for assigning a conversation to an agent", + "example": 1 + }, + "team_id": { + "type": "integer", + "description": "Team Id for assigning a conversation to a team\\", + "example": 1 + }, + "snoozed_until": { + "type": "string", + "format": "date-time", + "description": "Snoozed until date time", + "example": "2030-07-21T17:32:28Z" + }, + "message": { + "type": "object", + "description": "The initial message to be sent to the conversation", + "required": [ + "content" + ], + "properties": { + "content": { + "type": "string", + "description": "The content of the message", + "example": "Hello, how can I help you?" + }, + "template_params": { + "type": "object", + "description": "The template params for the message in case of whatsapp Channel", + "properties": { + "name": { + "type": "string", + "description": "Name of the template", + "example": "sample_issue_resolution" + }, + "category": { + "type": "string", + "description": "Category of the template", + "example": "UTILITY" + }, + "language": { + "type": "string", + "description": "Language of the template", + "example": "en_US" + }, + "processed_params": { + "type": "object", + "description": "The processed param values for template variables in template", + "example": { + "1": "Chatwoot" + } + } + } + } + } + } + } + }, + "conversation_message_create_payload": { + "type": "object", + "required": [ + "content" + ], + "properties": { + "content": { + "type": "string", + "description": "The content of the message", + "example": "Hello, how can I help you?" + }, + "message_type": { + "type": "string", + "enum": [ + "outgoing", + "incoming" + ], + "description": "The type of the message", + "example": "outgoing" + }, + "private": { + "type": "boolean", + "description": "Flag to identify if it is a private note", + "example": false + }, + "content_type": { + "type": "string", + "enum": [ + "text", + "input_email", + "cards", + "input_select", + "form", + "article" + ], + "description": "Content type of the message", + "example": "text" + }, + "content_attributes": { + "type": "object", + "description": "Attributes based on the content type", + "example": {} + }, + "campaign_id": { + "type": "integer", + "description": "The campaign id to which the message belongs", + "example": 1 + }, + "template_params": { + "type": "object", + "description": "The template params for the message in case of whatsapp Channel", + "properties": { + "name": { + "type": "string", + "description": "Name of the template", + "example": "sample_issue_resolution" + }, + "category": { + "type": "string", + "description": "Category of the template", + "example": "UTILITY" + }, + "language": { + "type": "string", + "description": "Language of the template", + "example": "en_US" + }, + "processed_params": { + "type": "object", + "description": "The processed param values for template variables in template", + "example": { + "1": "Chatwoot" + } + } + } + } + } + }, + "inbox_create_payload": { + "type": "object", + "properties": { + "name": { + "type": "string", + "description": "The name of the inbox", + "example": "Support" + }, + "avatar": { + "type": "string", + "format": "binary", + "description": "Image file for avatar" + }, + "greeting_enabled": { + "type": "boolean", + "description": "Enable greeting message", + "example": true + }, + "greeting_message": { + "type": "string", + "description": "Greeting message to be displayed on the widget", + "example": "Hello, how can I help you?" + }, + "enable_email_collect": { + "type": "boolean", + "description": "Enable email collection", + "example": true + }, + "csat_survey_enabled": { + "type": "boolean", + "description": "Enable CSAT survey", + "example": true + }, + "enable_auto_assignment": { + "type": "boolean", + "description": "Enable Auto Assignment", + "example": true + }, + "working_hours_enabled": { + "type": "boolean", + "description": "Enable working hours", + "example": true + }, + "out_of_office_message": { + "type": "string", + "description": "Out of office message to be displayed on the widget", + "example": "We are currently out of office. Please leave a message and we will get back to you." + }, + "timezone": { + "type": "string", + "description": "Timezone of the inbox", + "example": "America/New_York" + }, + "allow_messages_after_resolved": { + "type": "boolean", + "description": "Allow messages after conversation is resolved", + "example": true + }, + "lock_to_single_conversation": { + "type": "boolean", + "description": "Lock to single conversation", + "example": true + }, + "portal_id": { + "type": "integer", + "description": "Id of the help center portal to attach to the inbox", + "example": 1 + }, + "sender_name_type": { + "type": "string", + "description": "Sender name type for the inbox", + "enum": [ + "friendly", + "professional" + ], + "example": "friendly" + }, + "business_name": { + "type": "string", + "description": "Business name for the inbox", + "example": "My Business" + }, + "channel": { + "type": "object", + "properties": { + "type": { + "type": "string", + "description": "Type of the channel", + "enum": [ + "web_widget", + "api", + "email", + "line", + "telegram", + "whatsapp", + "sms" + ], + "example": "web_widget" + }, + "website_url": { + "type": "string", + "description": "URL at which the widget will be loaded", + "example": "https://example.com" + }, + "welcome_title": { + "type": "string", + "description": "Welcome title to be displayed on the widget", + "example": "Welcome to our support" + }, + "welcome_tagline": { + "type": "string", + "description": "Welcome tagline to be displayed on the widget", + "example": "We are here to help you" + }, + "widget_color": { + "type": "string", + "description": "A Hex-color string used to customize the widget", + "example": "#FF5733" + } + } + } + } + }, + "inbox_update_payload": { + "type": "object", + "properties": { + "name": { + "type": "string", + "description": "The name of the inbox", + "example": "Support" + }, + "avatar": { + "type": "string", + "format": "binary", + "description": "Image file for avatar" + }, + "greeting_enabled": { + "type": "boolean", + "description": "Enable greeting message", + "example": true + }, + "greeting_message": { + "type": "string", + "description": "Greeting message to be displayed on the widget", + "example": "Hello, how can I help you?" + }, + "enable_email_collect": { + "type": "boolean", + "description": "Enable email collection", + "example": true + }, + "csat_survey_enabled": { + "type": "boolean", + "description": "Enable CSAT survey", + "example": true + }, + "enable_auto_assignment": { + "type": "boolean", + "description": "Enable Auto Assignment", + "example": true + }, + "working_hours_enabled": { + "type": "boolean", + "description": "Enable working hours", + "example": true + }, + "out_of_office_message": { + "type": "string", + "description": "Out of office message to be displayed on the widget", + "example": "We are currently out of office. Please leave a message and we will get back to you." + }, + "timezone": { + "type": "string", + "description": "Timezone of the inbox", + "example": "America/New_York" + }, + "allow_messages_after_resolved": { + "type": "boolean", + "description": "Allow messages after conversation is resolved", + "example": true + }, + "lock_to_single_conversation": { + "type": "boolean", + "description": "Lock to single conversation", + "example": true + }, + "portal_id": { + "type": "integer", + "description": "Id of the help center portal to attach to the inbox", + "example": 1 + }, + "sender_name_type": { + "type": "string", + "description": "Sender name type for the inbox", + "enum": [ + "friendly", + "professional" + ], + "example": "friendly" + }, + "business_name": { + "type": "string", + "description": "Business name for the inbox", + "example": "My Business" + }, + "channel": { + "type": "object", + "properties": { + "website_url": { + "type": "string", + "description": "URL at which the widget will be loaded", + "example": "https://example.com" + }, + "welcome_title": { + "type": "string", + "description": "Welcome title to be displayed on the widget", + "example": "Welcome to our support" + }, + "welcome_tagline": { + "type": "string", + "description": "Welcome tagline to be displayed on the widget", + "example": "We are here to help you" + }, + "widget_color": { + "type": "string", + "description": "A Hex-color string used to customize the widget", + "example": "#FF5733" + } + } + } + } + }, + "team_create_update_payload": { + "type": "object", + "properties": { + "name": { + "type": "string", + "description": "The name of the team", + "example": "Support Team" + }, + "description": { + "type": "string", + "description": "The description of the team", + "example": "This is a team of support agents" + }, + "allow_auto_assign": { + "type": "boolean", + "description": "If this setting is turned on, the system would automatically assign the conversation to an agent in the team while assigning the conversation to a team", + "example": true + } + } + }, + "custom_filter_create_update_payload": { + "type": "object", + "properties": { + "name": { + "type": "string", + "description": "The name of the custom filter", + "example": "My Custom Filter" + }, + "type": { + "type": "string", + "enum": [ + "conversation", + "contact", + "report" + ], + "description": "The description about the custom filter", + "example": "conversation" + }, + "query": { + "type": "object", + "description": "A query that needs to be saved as a custom filter", + "example": {} + } + } + }, + "webhook_create_update_payload": { + "type": "object", + "properties": { + "url": { + "type": "string", + "description": "The url where the events should be sent", + "example": "https://example.com/webhook" + }, + "subscriptions": { + "type": "array", + "items": { + "type": "string", + "enum": [ + "conversation_created", + "conversation_status_changed", + "conversation_updated", + "message_created", + "message_updated", + "contact_created", + "contact_updated", + "webwidget_triggered" + ] + }, + "description": "The events you want to subscribe to.", + "example": [ + "conversation_created", + "conversation_status_changed" + ] + } + } + }, + "integrations_hook_create_payload": { + "type": "object", + "properties": { + "app_id": { + "type": "integer", + "description": "The ID of app for which integration hook is being created", + "example": 1 + }, + "inbox_id": { + "type": "integer", + "description": "The inbox ID, if the hook is an inbox hook", + "example": 1 + }, + "status": { + "type": "integer", + "description": "The status of the integration (0 for inactive, 1 for active)", + "example": 1 + }, + "settings": { + "type": "object", + "description": "The settings required by the integration", + "example": {} + } + } + }, + "integrations_hook_update_payload": { + "type": "object", + "properties": { + "status": { + "type": "integer", + "description": "The status of the integration (0 for inactive, 1 for active)", + "example": 1 + }, + "settings": { + "type": "object", + "description": "The settings required by the integration", + "example": {} + } + } + }, + "automation_rule_create_update_payload": { + "type": "object", + "properties": { + "name": { + "type": "string", + "description": "Rule name", + "example": "Add label on message create event" + }, + "description": { + "type": "string", + "description": "The description about the automation and actions", + "example": "Add label support and sales on message create event if incoming message content contains text help" + }, + "event_name": { + "type": "string", + "enum": [ + "conversation_created", + "conversation_updated", + "message_created" + ], + "example": "message_created", + "description": "The event when you want to execute the automation actions" + }, + "active": { + "type": "boolean", + "description": "Enable/disable automation rule" + }, + "actions": { + "type": "array", + "description": "Array of actions which you want to perform when condition matches, e.g add label support if message contains content help.", + "items": { + "type": "object", + "example": { + "action_name": "add_label", + "action_params": [ + "support" + ] + } + } + }, + "conditions": { + "type": "array", + "description": "Array of conditions on which conversation filter would work, e.g message content contains text help.", + "items": { + "type": "object", + "example": { + "attribute_key": "content", + "filter_operator": "contains", + "query_operator": "OR", + "values": [ + "help" + ] + } + } + } + } + }, + "portal_create_update_payload": { + "type": "object", + "properties": { + "color": { + "type": "string", + "description": "Header color for help-center in hex format", + "example": "#FFFFFF" + }, + "custom_domain": { + "type": "string", + "description": "Custom domain to display help center.", + "example": "chatwoot.help" + }, + "header_text": { + "type": "string", + "description": "Help center header", + "example": "Handbook" + }, + "homepage_link": { + "type": "string", + "description": "link to main dashboard", + "example": "https://www.chatwoot.com/" + }, + "name": { + "type": "string", + "description": "Name for the portal", + "example": "Handbook" + }, + "page_title": { + "type": "string", + "description": "Page title for the portal", + "example": "Handbook" + }, + "slug": { + "type": "string", + "description": "Slug for the portal to display in link", + "example": "handbook" + }, + "archived": { + "type": "boolean", + "description": "Status to check if portal is live", + "example": false + }, + "config": { + "type": "object", + "description": "Configuration about supporting locales", + "example": { + "allowed_locales": [ + "en", + "es" + ], + "default_locale": "en" + } + } + } + }, + "category_create_update_payload": { + "type": "object", + "properties": { + "name": { + "type": "string", + "description": "The name of the category", + "example": "Category Name" + }, + "description": { + "type": "string", + "description": "A description for the category", + "example": "Category description" + }, + "position": { + "type": "integer", + "description": "Category position in the portal list to sort", + "example": 1 + }, + "slug": { + "type": "string", + "description": "The category slug used in the URL", + "example": "category-name" + }, + "locale": { + "type": "string", + "description": "The locale of the category", + "example": "en" + }, + "icon": { + "type": "string", + "description": "The icon of the category as a string (emoji)", + "example": "📚" + }, + "parent_category_id": { + "type": "integer", + "description": "To define parent category, e.g product documentation has multiple level features in sales category or in engineering category.", + "example": 1 + }, + "associated_category_id": { + "type": "integer", + "description": "To associate similar categories to each other, e.g same category of product documentation in different languages", + "example": 2 + } + } + }, + "article_create_update_payload": { + "type": "object", + "properties": { + "title": { + "type": "string", + "description": "The title of the article", + "example": "Article Title" + }, + "slug": { + "type": "string", + "description": "The slug of the article", + "example": "article-title" + }, + "position": { + "type": "integer", + "description": "article position in category", + "example": 1 + }, + "content": { + "type": "string", + "description": "The text content.", + "example": "This is the content of the article" + }, + "description": { + "type": "string", + "description": "The description of the article", + "example": "This is the description of the article" + }, + "category_id": { + "type": "integer", + "description": "The category id of the article", + "example": 1 + }, + "author_id": { + "type": "integer", + "description": "The author agent id of the article", + "example": 1 + }, + "associated_article_id": { + "type": "integer", + "description": "To associate similar articles to each other, e.g to provide the link for the reference.", + "example": 2 + }, + "status": { + "type": "integer", + "description": "The status of the article. 0 for draft, 1 for published, 2 for archived", + "example": 1 + }, + "locale": { + "type": "string", + "description": "The locale of the article", + "example": "en" + }, + "meta": { + "type": "object", + "description": "Use for search", + "example": { + "tags": [ + "article_name" + ], + "title": "article title", + "description": "article description" + } + } + } + }, + "public_contact_create_update_payload": { + "type": "object", + "properties": { + "identifier": { + "type": "string", + "description": "External identifier of the contact", + "example": "1234567890" + }, + "identifier_hash": { + "type": "string", + "description": "Identifier hash prepared for HMAC authentication", + "example": "e93275d4eba0e5679ad55f5360af00444e2a888df9b0afa3e8b691c3173725f9" + }, + "email": { + "type": "string", + "description": "Email of the contact", + "example": "alice@acme.inc" + }, + "name": { + "type": "string", + "description": "Name of the contact", + "example": "Alice" + }, + "phone_number": { + "type": "string", + "description": "Phone number of the contact", + "example": "+123456789" + }, + "avatar": { + "type": "string", + "format": "binary", + "description": "Send the form data with the avatar image binary or use the avatar_url" + }, + "custom_attributes": { + "type": "object", + "description": "Custom attributes of the customer", + "example": {} + } + } + }, + "public_message_create_payload": { + "type": "object", + "properties": { + "content": { + "type": "string", + "description": "Content for the message", + "example": "Hello, how can I help you?" + }, + "echo_id": { + "type": "string", + "description": "Temporary identifier which will be passed back via websockets", + "example": "1234567890" + } + } + }, + "public_message_update_payload": { + "type": "object", + "properties": { + "submitted_values": { + "type": "object", + "description": "Replies to the Bot Message Types", + "properties": { + "name": { + "type": "string", + "description": "The name of the submiitted value", + "example": "My Name" + }, + "title": { + "type": "string", + "description": "The title of the submitted value", + "example": "My Title" + }, + "value": { + "type": "string", + "description": "The value of the submitted value", + "example": "value" + }, + "csat_survey_response": { + "type": "object", + "description": "The CSAT survey response", + "properties": { + "feedback_message": { + "type": "string", + "description": "The feedback message of the CSAT survey response", + "example": "Great service!" + }, + "rating": { + "type": "integer", + "description": "The rating of the CSAT survey response", + "example": 5 + } + } + } + } + } + } + }, + "public_conversation_create_payload": { + "type": "object", + "properties": { + "custom_attributes": { + "type": "object", + "description": "Custom attributes of the conversation", + "example": {} + } + } + }, + "extended_contact": { + "allOf": [ + { + "$ref": "#/components/schemas/contact" + }, + { + "type": "object", + "properties": { + "id": { + "type": "number", + "description": "Id of the user" + }, + "availability_status": { + "type": "string", + "enum": [ + "online", + "offline" + ], + "description": "Availability status of the user" + } + } + } + ] + }, + "contact_base": { + "allOf": [ + { + "$ref": "#/components/schemas/generic_id" + }, + { + "$ref": "#/components/schemas/contact" + } + ] + }, + "contact_list": { + "type": "array", + "description": "array of contacts", + "items": { + "allOf": [ + { + "$ref": "#/components/schemas/contact" + } + ] + } + }, + "contact_conversations": { + "type": "array", + "description": "array of conversations", + "items": { + "allOf": [ + { + "$ref": "#/components/schemas/conversation" + }, + { + "type": "object", + "properties": { + "meta": { + "type": "object", + "properties": { + "sender": { + "type": "object", + "properties": { + "additional_attributes": { + "type": "object", + "description": "The additional attributes of the sender" + }, + "availability_status": { + "type": "string", + "description": "The availability status of the sender" + }, + "email": { + "type": "string", + "description": "The email of the sender" + }, + "id": { + "type": "number", + "description": "ID fo the sender" + }, + "name": { + "type": "string", + "description": "The name of the sender" + }, + "phone_number": { + "type": "string", + "description": "The phone number of the sender" + }, + "blocked": { + "type": "boolean", + "description": "Whether the sender is blocked" + }, + "identifier": { + "type": "string", + "description": "The identifier of the sender" + }, + "thumbnail": { + "type": "string", + "description": "Avatar URL of the contact" + }, + "custom_attributes": { + "type": "object", + "description": "The custom attributes of the sender" + }, + "last_activity_at": { + "type": "number", + "description": "The last activity at of the sender" + }, + "created_at": { + "type": "number", + "description": "The created at of the sender" + } + } + }, + "channel": { + "type": "string", + "description": "Channel Type" + }, + "assignee": { + "$ref": "#/components/schemas/user" + }, + "hmac_verified": { + "type": "boolean", + "description": "Whether the hmac is verified" + } + } + } + } + }, + { + "type": "object", + "properties": { + "display_id": { + "type": "number" + } + } + } + ] + } + }, + "contact_labels": { + "type": "object", + "properties": { + "payload": { + "type": "array", + "description": "Array of labels", + "items": { + "type": "string" + } + } + } + }, + "conversation_list": { + "type": "object", + "properties": { + "data": { + "type": "object", + "properties": { + "meta": { + "type": "object", + "properties": { + "mine_count": { + "type": "number" + }, + "unassigned_count": { + "type": "number" + }, + "assigned_count": { + "type": "number" + }, + "all_count": { + "type": "number" + } + } + }, + "payload": { + "type": "array", + "description": "array of conversations", + "items": { + "allOf": [ + { + "$ref": "#/components/schemas/generic_id" + }, + { + "$ref": "#/components/schemas/conversation" + }, + { + "type": "object", + "properties": { + "meta": { + "type": "object", + "properties": { + "sender": { + "type": "object", + "properties": { + "additional_attributes": { + "type": "object", + "description": "The additional attributes of the sender" + }, + "availability_status": { + "type": "string", + "description": "The availability status of the sender" + }, + "email": { + "type": "string", + "description": "The email of the sender" + }, + "id": { + "type": "number", + "description": "ID fo the sender" + }, + "name": { + "type": "string", + "description": "The name of the sender" + }, + "phone_number": { + "type": "string", + "description": "The phone number of the sender" + }, + "blocked": { + "type": "boolean", + "description": "Whether the sender is blocked" + }, + "identifier": { + "type": "string", + "description": "The identifier of the sender" + }, + "thumbnail": { + "type": "string", + "description": "Avatar URL of the contact" + }, + "custom_attributes": { + "type": "object", + "description": "The custom attributes of the sender" + }, + "last_activity_at": { + "type": "number", + "description": "The last activity at of the sender" + }, + "created_at": { + "type": "number", + "description": "The created at of the sender" + } + } + }, + "channel": { + "type": "string", + "description": "Channel Type" + }, + "assignee": { + "$ref": "#/components/schemas/user" + }, + "hmac_verified": { + "type": "boolean", + "description": "Whether the hmac is verified" + } + } + } + } + } + ] + } + } + } + } + } + }, + "conversation_show": { + "type": "object", + "allOf": [ + { + "$ref": "#/components/schemas/conversation" + }, + { + "type": "object", + "properties": { + "meta": { + "type": "object", + "properties": { + "sender": { + "type": "object", + "properties": { + "additional_attributes": { + "type": "object", + "description": "The additional attributes of the sender" + }, + "availability_status": { + "type": "string", + "description": "The availability status of the sender" + }, + "email": { + "type": "string", + "description": "The email of the sender" + }, + "id": { + "type": "number", + "description": "ID fo the sender" + }, + "name": { + "type": "string", + "description": "The name of the sender" + }, + "phone_number": { + "type": "string", + "description": "The phone number of the sender" + }, + "blocked": { + "type": "boolean", + "description": "Whether the sender is blocked" + }, + "identifier": { + "type": "string", + "description": "The identifier of the sender" + }, + "thumbnail": { + "type": "string", + "description": "Avatar URL of the contact" + }, + "custom_attributes": { + "type": "object", + "description": "The custom attributes of the sender" + }, + "last_activity_at": { + "type": "number", + "description": "The last activity at of the sender" + }, + "created_at": { + "type": "number", + "description": "The created at of the sender" + } + } + }, + "channel": { + "type": "string", + "description": "Channel Type" + }, + "assignee": { + "$ref": "#/components/schemas/user" + }, + "hmac_verified": { + "type": "boolean", + "description": "Whether the hmac is verified" + } + } + } + } + } + ] + }, + "conversation_status_toggle": { + "type": "object", + "properties": { + "meta": { + "type": "object" + }, + "payload": { + "type": "object", + "properties": { + "success": { + "type": "boolean" + }, + "current_status": { + "type": "string", + "enum": [ + "open", + "resolved" + ] + }, + "conversation_id": { + "type": "number" + } + } + } + } + }, + "conversation_labels": { + "type": "object", + "properties": { + "payload": { + "type": "array", + "description": "Array of labels", + "items": { + "type": "string" + } + } + } + }, + "account_summary": { + "type": "object", + "properties": { + "avg_first_response_time": { + "type": "string" + }, + "avg_resolution_time": { + "type": "string" + }, + "conversations_count": { + "type": "number" + }, + "incoming_messages_count": { + "type": "number" + }, + "outgoing_messages_count": { + "type": "number" + }, + "resolutions_count": { + "type": "number" + }, + "previous": { + "type": "object", + "properties": { + "avg_first_response_time": { + "type": "string" + }, + "avg_resolution_time": { + "type": "string" + }, + "conversations_count": { + "type": "number" + }, + "incoming_messages_count": { + "type": "number" + }, + "outgoing_messages_count": { + "type": "number" + }, + "resolutions_count": { + "type": "number" + } + } + } + } + }, + "agent_conversation_metrics": { + "type": "object", + "properties": { + "id": { + "type": "number" + }, + "name": { + "type": "string" + }, + "email": { + "type": "string" + }, + "thumbnail": { + "type": "string" + }, + "availability": { + "type": "string" + }, + "metric": { + "type": "object", + "properties": { + "open": { + "type": "number" + }, + "unattended": { + "type": "number" + } + } + } + } + }, + "contact_detail": { + "type": "object", + "properties": { + "additional_attributes": { + "type": "object", + "description": "The object containing additional attributes related to the contact", + "properties": { + "city": { + "type": "string", + "description": "City of the contact" + }, + "country": { + "type": "string", + "description": "Country of the contact" + }, + "country_code": { + "type": "string", + "description": "Country code of the contact" + }, + "created_at_ip": { + "type": "string", + "description": "IP address when the contact was created" + } + } + }, + "custom_attributes": { + "type": "object", + "description": "The custom attributes of the contact" + }, + "email": { + "type": "string", + "description": "The email address of the contact" + }, + "id": { + "type": "integer", + "description": "The ID of the contact" + }, + "identifier": { + "type": "string", + "description": "The identifier of the contact", + "nullable": true + }, + "name": { + "type": "string", + "description": "The name of the contact" + }, + "phone_number": { + "type": "string", + "description": "The phone number of the contact", + "nullable": true + }, + "thumbnail": { + "type": "string", + "description": "The thumbnail of the contact" + }, + "blocked": { + "type": "boolean", + "description": "Whether the contact is blocked" + }, + "type": { + "type": "string", + "description": "The type of entity", + "enum": [ + "contact" + ] + } + } + }, + "message_detailed": { + "type": "object", + "properties": { + "id": { + "type": "number", + "description": "The ID of the message" + }, + "content": { + "type": "string", + "description": "The text content of the message" + }, + "inbox_id": { + "type": "number", + "description": "The ID of the inbox" + }, + "conversation_id": { + "type": "number", + "description": "The ID of the conversation" + }, + "message_type": { + "type": "integer", + "enum": [ + 0, + 1, + 2, + 3 + ], + "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" + ], + "description": "The type of the message content" + }, + "status": { + "type": "string", + "enum": [ + "sent", + "delivered", + "read", + "failed" + ], + "description": "The status of the message" + }, + "content_attributes": { + "type": "object", + "description": "The content attributes for each content_type", + "properties": { + "in_reply_to": { + "type": "string", + "description": "ID of the message this is replying to", + "nullable": true + } + } + }, + "created_at": { + "type": "integer", + "description": "The timestamp when message was created" + }, + "private": { + "type": "boolean", + "description": "The flag which shows whether the message is private or not" + }, + "source_id": { + "type": "string", + "description": "The source ID of the message", + "nullable": true + }, + "sender": { + "$ref": "#/components/schemas/contact_detail" + } + } + }, + "conversation_meta": { + "type": "object", + "properties": { + "labels": { + "type": "array", + "items": { + "type": "string" + }, + "description": "Labels associated with the conversation" + }, + "additional_attributes": { + "type": "object", + "properties": { + "browser": { + "type": "object", + "properties": { + "device_name": { + "type": "string", + "description": "Name of the device" + }, + "browser_name": { + "type": "string", + "description": "Name of the browser" + }, + "platform_name": { + "type": "string", + "description": "Name of the platform" + }, + "browser_version": { + "type": "string", + "description": "Version of the browser" + }, + "platform_version": { + "type": "string", + "description": "Version of the platform" + } + } + }, + "referer": { + "type": "string", + "description": "Referrer URL" + }, + "initiated_at": { + "type": "object", + "properties": { + "timestamp": { + "type": "string", + "description": "Timestamp when the conversation was initiated" + } + } + }, + "browser_language": { + "type": "string", + "description": "Browser language setting" + }, + "conversation_language": { + "type": "string", + "description": "Conversation language" + } + }, + "description": "Additional attributes of the conversation" + }, + "contact": { + "$ref": "#/components/schemas/contact_detail" + }, + "agent_last_seen_at": { + "type": "string", + "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 + } + } + }, + "conversation_messages": { + "type": "object", + "properties": { + "meta": { + "$ref": "#/components/schemas/conversation_meta" + }, + "payload": { + "type": "array", + "items": { + "$ref": "#/components/schemas/message_detailed" + }, + "description": "List of messages in the conversation" + } + } + }, + "contact_meta": { + "type": "object", + "properties": { + "count": { + "type": "integer", + "description": "Total number of contacts" + }, + "current_page": { + "type": "string", + "description": "Current page number" + } + } + }, + "contact_inbox": { + "type": "object", + "properties": { + "source_id": { + "type": "string", + "description": "Source identifier for the contact inbox" + }, + "inbox": { + "type": "object", + "properties": { + "id": { + "type": "integer", + "description": "ID of the inbox" + }, + "avatar_url": { + "type": "string", + "description": "URL for the inbox avatar" + }, + "channel_id": { + "type": "integer", + "description": "ID of the channel" + }, + "name": { + "type": "string", + "description": "Name of the inbox" + }, + "channel_type": { + "type": "string", + "description": "Type of channel" + }, + "provider": { + "type": "string", + "description": "Provider of the inbox", + "nullable": true + } + } + } + } + }, + "contact_list_item": { + "type": "object", + "properties": { + "additional_attributes": { + "type": "object", + "description": "The object containing additional attributes related to the contact", + "properties": { + "city": { + "type": "string", + "description": "City of the contact" + }, + "country": { + "type": "string", + "description": "Country of the contact" + }, + "country_code": { + "type": "string", + "description": "Country code of the contact" + }, + "created_at_ip": { + "type": "string", + "description": "IP address when the contact was created" + } + } + }, + "availability_status": { + "type": "string", + "description": "Availability status of the contact", + "enum": [ + "online", + "offline" + ] + }, + "email": { + "type": "string", + "description": "The email address of the contact", + "nullable": true + }, + "id": { + "type": "integer", + "description": "The ID of the contact" + }, + "name": { + "type": "string", + "description": "The name of the contact" + }, + "phone_number": { + "type": "string", + "description": "The phone number of the contact", + "nullable": true + }, + "blocked": { + "type": "boolean", + "description": "Whether the contact is blocked" + }, + "identifier": { + "type": "string", + "description": "The identifier of the contact", + "nullable": true + }, + "thumbnail": { + "type": "string", + "description": "The thumbnail of the contact" + }, + "custom_attributes": { + "type": "object", + "description": "The custom attributes of the contact" + }, + "last_activity_at": { + "type": "integer", + "description": "Timestamp of last activity", + "nullable": true + }, + "created_at": { + "type": "integer", + "description": "Timestamp when contact was created" + }, + "contact_inboxes": { + "type": "array", + "description": "List of inboxes associated with this contact", + "items": { + "$ref": "#/components/schemas/contact_inbox" + } + } + } + }, + "contacts_list_response": { + "type": "object", + "properties": { + "meta": { + "$ref": "#/components/schemas/contact_meta" + }, + "payload": { + "type": "array", + "items": { + "$ref": "#/components/schemas/contact_list_item" + }, + "description": "List of contacts" + } + } + }, + "contact_show_response": { + "type": "object", + "properties": { + "payload": { + "$ref": "#/components/schemas/contact_list_item" + } + } + }, + "contact_conversation_message": { + "type": "object", + "properties": { + "id": { + "type": "integer", + "description": "ID of the message" + }, + "content": { + "type": "string", + "description": "Content of the message" + }, + "account_id": { + "type": "integer", + "description": "ID of the account" + }, + "inbox_id": { + "type": "integer", + "description": "ID of the inbox" + }, + "conversation_id": { + "type": "integer", + "description": "ID of the conversation" + }, + "message_type": { + "type": "integer", + "description": "Type of the message" + }, + "created_at": { + "type": "integer", + "description": "Timestamp when message was created" + }, + "updated_at": { + "type": "string", + "description": "Formatted datetime when message was updated" + }, + "private": { + "type": "boolean", + "description": "Whether the message is private" + }, + "status": { + "type": "string", + "description": "Status of the message" + }, + "source_id": { + "type": "string", + "description": "Source ID of the message", + "nullable": true + }, + "content_type": { + "type": "string", + "description": "Type of the content" + }, + "content_attributes": { + "type": "object", + "description": "Attributes of the content" + }, + "sender_type": { + "type": "string", + "description": "Type of the sender", + "nullable": true + }, + "sender_id": { + "type": "integer", + "description": "ID of the sender", + "nullable": true + }, + "external_source_ids": { + "type": "object", + "description": "External source IDs" + }, + "additional_attributes": { + "type": "object", + "description": "Additional attributes of the message" + }, + "processed_message_content": { + "type": "string", + "description": "Processed message content", + "nullable": true + }, + "sentiment": { + "type": "object", + "description": "Sentiment analysis of the message" + }, + "conversation": { + "type": "object", + "description": "Conversation details", + "properties": { + "assignee_id": { + "type": "integer", + "description": "ID of the assignee", + "nullable": true + }, + "unread_count": { + "type": "integer", + "description": "Count of unread messages" + }, + "last_activity_at": { + "type": "integer", + "description": "Timestamp of last activity" + }, + "contact_inbox": { + "type": "object", + "description": "Contact inbox details", + "properties": { + "source_id": { + "type": "string", + "description": "Source ID of the contact inbox" + } + } + } + } + }, + "sender": { + "type": "object", + "description": "Details of the sender", + "properties": { + "id": { + "type": "integer", + "description": "ID of the sender" + }, + "name": { + "type": "string", + "description": "Name of the sender" + }, + "available_name": { + "type": "string", + "description": "Available name of the sender" + }, + "avatar_url": { + "type": "string", + "description": "URL of the sender's avatar" + }, + "type": { + "type": "string", + "description": "Type of the sender" + }, + "availability_status": { + "type": "string", + "description": "Availability status of the sender" + }, + "thumbnail": { + "type": "string", + "description": "Thumbnail URL of the sender" + } + } + } + } + }, + "contact_conversations_response": { + "type": "object", + "properties": { + "payload": { + "type": "array", + "items": { + "allOf": [ + { + "$ref": "#/components/schemas/conversation" + }, + { + "type": "object", + "properties": { + "meta": { + "type": "object", + "properties": { + "sender": { + "type": "object", + "properties": { + "additional_attributes": { + "type": "object", + "description": "The additional attributes of the sender" + }, + "availability_status": { + "type": "string", + "description": "The availability status of the sender" + }, + "email": { + "type": "string", + "description": "The email of the sender" + }, + "id": { + "type": "number", + "description": "ID fo the sender" + }, + "name": { + "type": "string", + "description": "The name of the sender" + }, + "phone_number": { + "type": "string", + "description": "The phone number of the sender" + }, + "blocked": { + "type": "boolean", + "description": "Whether the sender is blocked" + }, + "identifier": { + "type": "string", + "description": "The identifier of the sender" + }, + "thumbnail": { + "type": "string", + "description": "Avatar URL of the contact" + }, + "custom_attributes": { + "type": "object", + "description": "The custom attributes of the sender" + }, + "last_activity_at": { + "type": "number", + "description": "The last activity at of the sender" + }, + "created_at": { + "type": "number", + "description": "The created at of the sender" + } + } + }, + "channel": { + "type": "string", + "description": "Channel Type" + }, + "assignee": { + "$ref": "#/components/schemas/user" + }, + "hmac_verified": { + "type": "boolean", + "description": "Whether the hmac is verified" + } + } + } + } + } + ] + }, + "description": "List of conversations for the contact" + } + } + }, + "contactable_inboxes_response": { + "type": "object", + "properties": { + "payload": { + "type": "array", + "items": { + "$ref": "#/components/schemas/contact_inbox" + }, + "description": "List of contactable inboxes for the contact" + } + } + } + }, + "parameters": { + "account_id": { + "in": "path", + "name": "account_id", + "schema": { + "type": "integer" + }, + "required": true, + "description": "The numeric ID of the account" + }, + "agent_bot_id": { + "in": "path", + "name": "id", + "schema": { + "type": "integer" + }, + "required": true, + "description": "The ID of the agentbot to be updated" + }, + "team_id": { + "in": "path", + "name": "team_id", + "schema": { + "type": "integer" + }, + "required": true, + "description": "The ID of the team to be updated" + }, + "inbox_id": { + "in": "path", + "name": "inbox_id", + "schema": { + "type": "integer" + }, + "required": true, + "description": "The ID of the Inbox" + }, + "hook_id": { + "in": "path", + "name": "hook_id", + "schema": { + "type": "integer" + }, + "required": true, + "description": "The numeric ID of the integration hook" + }, + "source_id": { + "in": "path", + "name": "source_id", + "required": true, + "schema": { + "type": "string" + }, + "description": "Id of the session for which the conversation is created.\n\n\n\n Source Ids can be obtained through contactable inboxes API or via generated.Website: Chatwoot generated string which can be obtained from webhook events. Phone Channels(Twilio): Phone number in e164 format Email Channels: Contact Email address API Channel: Any Random String" + }, + "contact_sort_param": { + "in": "query", + "name": "sort", + "schema": { + "type": "string", + "enum": [ + "name", + "email", + "phone_number", + "last_activity_at", + "-name", + "-email", + "-phone_number", + "-last_activity_at" + ] + }, + "required": false, + "description": "The attribute by which list should be sorted" + }, + "conversation_id": { + "in": "path", + "name": "conversation_id", + "schema": { + "type": "integer" + }, + "required": true, + "description": "The numeric ID of the conversation" + }, + "conversation_uuid": { + "in": "path", + "name": "conversation_uuid", + "schema": { + "type": "integer" + }, + "required": true, + "description": "The uuid of the conversation" + }, + "custom_filter_id": { + "in": "path", + "name": "custom_filter_id", + "schema": { + "type": "integer" + }, + "required": true, + "description": "The numeric ID of the custom filter" + }, + "webhook_id": { + "in": "path", + "name": "webhook_id", + "schema": { + "type": "integer" + }, + "required": true, + "description": "The numeric ID of the webhook" + }, + "message_id": { + "in": "path", + "name": "message_id", + "schema": { + "type": "integer" + }, + "required": true, + "description": "The numeric ID of the message" + }, + "page": { + "in": "query", + "name": "page", + "schema": { + "type": "integer", + "default": 1 + }, + "required": false, + "description": "The page parameter" + }, + "platform_user_id": { + "in": "path", + "name": "id", + "schema": { + "type": "integer" + }, + "required": true, + "description": "The numeric ID of the user on the platform" + }, + "report_type": { + "in": "query", + "name": "type", + "schema": { + "type": "string", + "enum": [ + "account", + "agent", + "inbox", + "label", + "team" + ] + }, + "required": true, + "description": "Type of report" + }, + "report_metric": { + "in": "query", + "name": "metric", + "schema": { + "type": "string", + "enum": [ + "conversations_count", + "incoming_messages_count", + "outgoing_messages_count", + "avg_first_response_time", + "avg_resolution_time", + "resolutions_count" + ] + }, + "required": true, + "description": "The type of metric" + }, + "public_inbox_identifier": { + "in": "path", + "name": "inbox_identifier", + "schema": { + "type": "string" + }, + "required": true, + "description": "The identifier obtained from API inbox channel" + }, + "public_contact_identifier": { + "in": "path", + "name": "contact_identifier", + "schema": { + "type": "string" + }, + "required": true, + "description": "The source id of contact obtained on contact create" + }, + "portal_id": { + "in": "path", + "name": "id", + "schema": { + "type": "string" + }, + "required": true, + "description": "The slug identifier of the portal" + } + }, + "securitySchemes": { + "userApiKey": { + "type": "apiKey", + "in": "header", + "name": "api_access_token", + "description": "This token can be obtained by visiting the profile page or via rails console. Provides access to endpoints based on the user permissions levels. This token can be saved by an external system when user is created via API, to perform activities on behalf of the user." + }, + "agentBotApiKey": { + "type": "apiKey", + "in": "header", + "name": "api_access_token", + "description": "This token should be provided by system admin or obtained via rails console. This token can be used to build bot integrations and can only access limited apis." + }, + "platformAppApiKey": { + "type": "apiKey", + "in": "header", + "name": "api_access_token", + "description": "This token can be obtained by the system admin after creating a platformApp. This token should be used to provision agent bots, accounts, users and their roles." + } + } + }, + "tags": [ + { + "name": "CSAT Survey Page", + "description": "Customer satisfaction survey" + } + ], + "x-tagGroups": [ + { + "name": "Platform", + "tags": [ + "Accounts", + "Account Users", + "AgentBots", + "Users" + ] + }, + { + "name": "Application", + "tags": [ + "Account AgentBots", + "Agents", + "Canned Responses", + "Contacts", + "Contact Labels", + "Conversation Assignments", + "Conversation Labels", + "Conversations", + "Custom Attributes", + "Custom Filters", + "Inboxes", + "Integrations", + "Messages", + "Profile", + "Reports", + "Teams", + "Webhooks", + "Automation Rule", + "Help Center" + ] + }, + { + "name": "Client", + "tags": [ + "Contacts API", + "Conversations API", + "Messages API" + ] + }, + { + "name": "Others", + "tags": [ + "CSAT Survey Page" + ] + } + ] +} \ No newline at end of file diff --git a/swagger/tag_groups/others.yml b/swagger/tag_groups/others.yml new file mode 100644 index 000000000..08219959c --- /dev/null +++ b/swagger/tag_groups/others.yml @@ -0,0 +1,29 @@ +openapi: '3.0.4' +info: + title: Chatwoot - Other APIs + description: Other API endpoints for Chatwoot + version: 1.1.0 + termsOfService: https://www.chatwoot.com/terms-of-service/ + contact: + email: hello@chatwoot.com + license: + name: MIT License + url: https://opensource.org/licenses/MIT +servers: + - url: https://app.chatwoot.com/ +tags: + - name: CSAT Survey Page + description: APIs for CSAT survey functionality +paths: + $ref: ../paths/index.yml +components: + schemas: + $ref: ../definitions/index.yml + parameters: + $ref: ../parameters/index.yml + securitySchemes: + userApiKey: + type: apiKey + in: header + name: api_access_token + description: This token can be obtained by visiting the profile page or via rails console. Provides access to endpoints based on the user permissions levels. \ No newline at end of file diff --git a/swagger/tag_groups/platform.yml b/swagger/tag_groups/platform.yml new file mode 100644 index 000000000..139dd741e --- /dev/null +++ b/swagger/tag_groups/platform.yml @@ -0,0 +1,40 @@ +openapi: '3.0.4' +info: + title: Chatwoot - Platform API + description: Platform API endpoints for Chatwoot + version: 1.1.0 + termsOfService: https://www.chatwoot.com/terms-of-service/ + contact: + email: hello@chatwoot.com + license: + name: MIT License + url: https://opensource.org/licenses/MIT +servers: + - url: https://app.chatwoot.com/ +tags: + - name: Accounts + description: Platform account management + - name: Account Users + description: Manage users within platform accounts + - name: AgentBots + description: Manage agent bots on the platform + - name: Users + description: Platform user management +paths: + $ref: ../paths/index.yml +components: + schemas: + $ref: ../definitions/index.yml + parameters: + $ref: ../parameters/index.yml + securitySchemes: + userApiKey: + type: apiKey + in: header + name: api_access_token + description: This token can be obtained by visiting the profile page or via rails console. Provides access to endpoints based on the user permissions levels. + platformAppApiKey: + type: apiKey + in: header + name: api_access_token + description: This token can be obtained by the system admin after creating a platformApp. This token should be used to provision agent bots, accounts, users and their roles. \ No newline at end of file diff --git a/swagger/tag_groups/platform_swagger.json b/swagger/tag_groups/platform_swagger.json new file mode 100644 index 000000000..18ec796a0 --- /dev/null +++ b/swagger/tag_groups/platform_swagger.json @@ -0,0 +1,5143 @@ +{ + "openapi": "3.0.4", + "info": { + "title": "Chatwoot", + "description": "This is the API documentation for Chatwoot server.", + "version": "1.1.0", + "termsOfService": "https://www.chatwoot.com/terms-of-service/", + "contact": { + "email": "hello@chatwoot.com" + }, + "license": { + "name": "MIT License", + "url": "https://opensource.org/licenses/MIT" + } + }, + "servers": [ + { + "url": "https://app.chatwoot.com/" + } + ], + "paths": { + "/platform/api/v1/accounts": { + "post": { + "tags": [ + "Accounts" + ], + "operationId": "create-an-account", + "summary": "Create an Account", + "description": "Create an Account", + "security": [ + { + "platformAppApiKey": [] + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/account_create_update_payload" + } + } + } + }, + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/platform_account" + } + } + } + }, + "401": { + "description": "Unauthorized", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/bad_request_error" + } + } + } + } + } + } + }, + "/platform/api/v1/accounts/{account_id}": { + "parameters": [ + { + "$ref": "#/components/parameters/account_id" + } + ], + "get": { + "tags": [ + "Accounts" + ], + "operationId": "get-details-of-an-account", + "summary": "Get an account details", + "description": "Get the details of an account", + "security": [ + { + "platformAppApiKey": [] + } + ], + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/platform_account" + } + } + } + }, + "401": { + "description": "Unauthorized", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/bad_request_error" + } + } + } + }, + "404": { + "description": "The given account does not exist", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/bad_request_error" + } + } + } + } + } + }, + "patch": { + "tags": [ + "Accounts" + ], + "operationId": "update-an-account", + "summary": "Update an account", + "description": "Update an account's attributes", + "security": [ + { + "platformAppApiKey": [] + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/account_create_update_payload" + } + } + } + }, + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/platform_account" + } + } + } + }, + "401": { + "description": "Unauthorized", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/bad_request_error" + } + } + } + } + } + }, + "delete": { + "tags": [ + "Accounts" + ], + "operationId": "delete-an-account", + "summary": "Delete an Account", + "description": "Delete an Account", + "security": [ + { + "platformAppApiKey": [] + } + ], + "responses": { + "200": { + "description": "Success" + }, + "401": { + "description": "Unauthorized", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/bad_request_error" + } + } + } + }, + "404": { + "description": "The account does not exist", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/bad_request_error" + } + } + } + } + } + } + }, + "/platform/api/v1/accounts/{account_id}/account_users": { + "parameters": [ + { + "$ref": "#/components/parameters/account_id" + } + ], + "get": { + "tags": [ + "Account Users" + ], + "operationId": "list-all-account-users", + "summary": "List all Account Users", + "description": "List all account users", + "security": [ + { + "platformAppApiKey": [] + } + ], + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/account_user" + } + } + } + }, + "401": { + "description": "Unauthorized", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/bad_request_error" + } + } + } + } + } + }, + "post": { + "tags": [ + "Account Users" + ], + "operationId": "create-an-account-user", + "summary": "Create an Account User", + "description": "Create an Account User", + "security": [ + { + "platformAppApiKey": [] + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/account_user_create_update_payload" + } + } + } + }, + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "account_id": { + "type": "integer", + "description": "The ID of the account" + }, + "user_id": { + "type": "integer", + "description": "The ID of the user" + }, + "role": { + "type": "string", + "description": "whether user is an administrator or agent" + } + } + } + } + } + }, + "401": { + "description": "Unauthorized", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/bad_request_error" + } + } + } + } + } + }, + "delete": { + "tags": [ + "Account Users" + ], + "operationId": "delete-an-account-user", + "summary": "Delete an Account User", + "description": "Delete an Account User", + "security": [ + { + "platformAppApiKey": [] + } + ], + "responses": { + "200": { + "description": "Success" + }, + "401": { + "description": "Unauthorized", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/bad_request_error" + } + } + } + }, + "404": { + "description": "The account does not exist", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/bad_request_error" + } + } + } + } + } + } + }, + "/platform/api/v1/agent_bots": { + "get": { + "tags": [ + "AgentBots" + ], + "operationId": "list-all-agent-bots", + "summary": "List all AgentBots", + "description": "List all agent bots available", + "security": [ + { + "platformAppApiKey": [] + } + ], + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "type": "array", + "description": "Array of agent bots", + "items": { + "$ref": "#/components/schemas/agent_bot" + } + } + } + } + }, + "401": { + "description": "Unauthorized", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/bad_request_error" + } + } + } + } + } + }, + "post": { + "tags": [ + "AgentBots" + ], + "operationId": "create-an-agent-bot", + "summary": "Create an Agent Bot", + "description": "Create an agent bot", + "security": [ + { + "platformAppApiKey": [] + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/platform_agent_bot_create_update_payload" + } + } + } + }, + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/agent_bot" + } + } + } + }, + "401": { + "description": "Unauthorized", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/bad_request_error" + } + } + } + } + } + } + }, + "/platform/api/v1/agent_bots/{id}": { + "parameters": [ + { + "$ref": "#/components/parameters/agent_bot_id" + } + ], + "get": { + "tags": [ + "AgentBots" + ], + "operationId": "get-details-of-a-single-agent-bot", + "summary": "Get an agent bot details", + "description": "Get the details of an agent bot", + "security": [ + { + "platformAppApiKey": [] + } + ], + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/agent_bot" + } + } + } + }, + "401": { + "description": "Unauthorized", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/bad_request_error" + } + } + } + }, + "404": { + "description": "The given agent bot ID does not exist", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/bad_request_error" + } + } + } + } + } + }, + "patch": { + "tags": [ + "AgentBots" + ], + "operationId": "update-an-agent-bot", + "summary": "Update an agent bot", + "description": "Update an agent bot's attributes", + "security": [ + { + "platformAppApiKey": [] + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/platform_agent_bot_create_update_payload" + } + } + } + }, + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/agent_bot" + } + } + } + }, + "401": { + "description": "Unauthorized", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/bad_request_error" + } + } + } + } + } + }, + "delete": { + "tags": [ + "AgentBots" + ], + "operationId": "delete-an-agent-bot", + "summary": "Delete an AgentBot", + "description": "Delete an AgentBot", + "security": [ + { + "platformAppApiKey": [] + } + ], + "responses": { + "200": { + "description": "Success" + }, + "401": { + "description": "Unauthorized", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/bad_request_error" + } + } + } + }, + "404": { + "description": "The agent bot does not exist", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/bad_request_error" + } + } + } + } + } + } + }, + "/platform/api/v1/users": { + "post": { + "tags": [ + "Users" + ], + "operationId": "create-a-user", + "summary": "Create a User", + "description": "Create a User", + "security": [ + { + "platformAppApiKey": [] + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/user_create_update_payload" + } + } + } + }, + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/user" + } + } + } + }, + "401": { + "description": "Unauthorized", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/bad_request_error" + } + } + } + } + } + } + }, + "/platform/api/v1/users/{id}": { + "parameters": [ + { + "$ref": "#/components/parameters/platform_user_id" + } + ], + "get": { + "tags": [ + "Users" + ], + "operationId": "get-details-of-a-user", + "summary": "Get an user details", + "description": "Get the details of an user", + "security": [ + { + "platformAppApiKey": [] + } + ], + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/user" + } + } + } + }, + "401": { + "description": "Unauthorized", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/bad_request_error" + } + } + } + }, + "404": { + "description": "The given user does not exist", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/bad_request_error" + } + } + } + } + } + }, + "patch": { + "tags": [ + "Users" + ], + "operationId": "update-a-user", + "summary": "Update a user", + "description": "Update a user's attributes", + "security": [ + { + "platformAppApiKey": [] + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/user_create_update_payload" + } + } + } + }, + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/user" + } + } + } + }, + "401": { + "description": "Unauthorized", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/bad_request_error" + } + } + } + } + } + }, + "delete": { + "tags": [ + "Users" + ], + "operationId": "delete-a-user", + "summary": "Delete a User", + "description": "Delete a User", + "security": [ + { + "platformAppApiKey": [] + } + ], + "responses": { + "200": { + "description": "Success" + }, + "401": { + "description": "Unauthorized", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/bad_request_error" + } + } + } + }, + "404": { + "description": "The user does not exist", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/bad_request_error" + } + } + } + } + } + } + }, + "/platform/api/v1/users/{id}/login": { + "parameters": [ + { + "$ref": "#/components/parameters/platform_user_id" + } + ], + "get": { + "tags": [ + "Users" + ], + "operationId": "get-sso-url-of-a-user", + "summary": "Get User SSO Link", + "description": "Get the sso link of a user", + "security": [ + { + "platformAppApiKey": [] + } + ], + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "url": { + "type": "string", + "description": "SSO url to autenticate the user" + } + } + } + } + } + }, + "401": { + "description": "Unauthorized", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/bad_request_error" + } + } + } + }, + "404": { + "description": "The given user does not exist", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/bad_request_error" + } + } + } + } + } + } + } + }, + "components": { + "schemas": { + "bad_request_error": { + "title": "data", + "type": "object", + "properties": { + "description": { + "type": "string" + }, + "errors": { + "type": "array", + "items": { + "$ref": "#/components/schemas/request_error" + } + } + } + }, + "request_error": { + "type": "object", + "properties": { + "field": { + "type": "string" + }, + "message": { + "type": "string" + }, + "code": { + "type": "string" + } + } + }, + "generic_id": { + "type": "object", + "properties": { + "id": { + "type": "number" + } + } + }, + "canned_response": { + "type": "object", + "properties": { + "id": { + "type": "integer", + "description": "ID of the canned response" + }, + "account_id": { + "type": "integer", + "description": "Account Id" + }, + "short_code": { + "type": "string", + "description": "Short Code for quick access of the canned response" + }, + "content": { + "type": "string", + "description": "Message content for canned response" + }, + "created_at": { + "type": "string", + "description": "The date and time when the canned response was created" + }, + "updated_at": { + "type": "string", + "description": "The date and time when the canned response was updated" + } + } + }, + "custom_attribute": { + "type": "object", + "properties": { + "id": { + "type": "integer", + "description": "Identifier" + }, + "attribute_display_name": { + "type": "string", + "description": "Attribute display name" + }, + "attribute_display_type": { + "type": "string", + "description": "Attribute display type (text, number, currency, percent, link, date, list, checkbox)" + }, + "attribute_description": { + "type": "string", + "description": "Attribute description" + }, + "attribute_key": { + "type": "string", + "description": "Attribute unique key value" + }, + "regex_pattern": { + "type": "string", + "description": "Regex pattern" + }, + "regex_cue": { + "type": "string", + "description": "Regex cue" + }, + "attribute_values": { + "type": "string", + "description": "Attribute values" + }, + "attribute_model": { + "type": "string", + "description": "Attribute type(conversation_attribute/contact_attribute)" + }, + "default_value": { + "type": "string", + "description": "Attribute default value" + }, + "created_at": { + "type": "string", + "description": "The date and time when the custom attribute was created" + }, + "updated_at": { + "type": "string", + "description": "The date and time when the custom attribute was updated" + } + } + }, + "automation_rule": { + "type": "object", + "properties": { + "payload": { + "description": "Response payload that contains automation rule(s)", + "oneOf": [ + { + "type": "array", + "description": "Array of automation rules (for listing endpoint)", + "items": { + "$ref": "#/components/schemas/automation_rule_item" + } + }, + { + "type": "object", + "description": "Single automation rule (for show/create/update endpoints)", + "allOf": [ + { + "$ref": "#/components/schemas/automation_rule_item" + } + ] + } + ] + } + } + }, + "automation_rule_item": { + "type": "object", + "properties": { + "id": { + "type": "integer", + "description": "The ID of the automation rule" + }, + "account_id": { + "type": "integer", + "description": "Account Id" + }, + "name": { + "type": "string", + "description": "The name of the rule", + "example": "Add label on message create event" + }, + "description": { + "type": "string", + "description": "Description to give more context about the rule", + "example": "Add label support and sales on message create event if incoming message content contains text help" + }, + "event_name": { + "type": "string", + "description": "Automation Rule event, on which we call the actions(conversation_created, conversation_updated, message_created)", + "enum": [ + "conversation_created", + "conversation_updated", + "message_created" + ], + "example": "message_created" + }, + "conditions": { + "type": "array", + "description": "Array of conditions on which conversation/message filter would work", + "items": { + "type": "object", + "properties": { + "values": { + "type": "array", + "items": { + "type": "string" + } + }, + "attribute_key": { + "type": "string" + }, + "query_operator": { + "type": "string" + }, + "filter_operator": { + "type": "string" + } + }, + "example": { + "attribute_key": "content", + "filter_operator": "contains", + "values": [ + "help" + ], + "query_operator": "and" + } + } + }, + "actions": { + "type": "array", + "description": "Array of actions which we perform when condition matches", + "items": { + "type": "object", + "properties": { + "action_name": { + "type": "string" + }, + "action_params": { + "type": "array", + "items": { + "type": "string" + } + } + }, + "example": { + "action_name": "add_label", + "action_params": [ + "support", + "sales" + ] + } + } + }, + "created_on": { + "type": "integer", + "description": "The timestamp when the rule was created" + }, + "active": { + "type": "boolean", + "description": "Enable/disable automation rule" + } + } + }, + "portal": { + "type": "object", + "properties": { + "payload": { + "type": "array", + "items": { + "$ref": "#/components/schemas/portal_item" + } + } + } + }, + "portal_single": { + "type": "object", + "properties": { + "payload": { + "$ref": "#/components/schemas/portal_item" + } + } + }, + "portal_config": { + "type": "object", + "description": "Configuration settings for the portal", + "properties": { + "allowed_locales": { + "type": "array", + "description": "List of allowed locales for the portal", + "items": { + "type": "object", + "properties": { + "code": { + "type": "string", + "description": "The language code" + }, + "articles_count": { + "type": "integer", + "description": "Number of articles in this locale" + }, + "categories_count": { + "type": "integer", + "description": "Number of categories in this locale" + } + } + } + } + } + }, + "portal_logo": { + "type": "object", + "properties": { + "id": { + "type": "integer", + "description": "ID of the logo file" + }, + "portal_id": { + "type": "integer", + "description": "ID of the portal this logo belongs to" + }, + "file_type": { + "type": "string", + "description": "MIME type of the file" + }, + "account_id": { + "type": "integer", + "description": "ID of the account" + }, + "file_url": { + "type": "string", + "description": "URL to access the logo file" + }, + "blob_id": { + "type": "integer", + "description": "ID of the blob" + }, + "filename": { + "type": "string", + "description": "Name of the file" + } + } + }, + "portal_meta": { + "type": "object", + "properties": { + "all_articles_count": { + "type": "integer", + "description": "Total number of articles" + }, + "archived_articles_count": { + "type": "integer", + "nullable": true, + "description": "Number of archived articles" + }, + "published_count": { + "type": "integer", + "nullable": true, + "description": "Number of published articles" + }, + "draft_articles_count": { + "type": "integer", + "nullable": true, + "description": "Number of draft articles" + }, + "categories_count": { + "type": "integer", + "description": "Number of categories" + }, + "default_locale": { + "type": "string", + "description": "Default locale for the portal" + } + } + }, + "portal_item": { + "type": "object", + "properties": { + "id": { + "type": "integer", + "description": "The ID of the portal" + }, + "archived": { + "type": "boolean", + "description": "Whether the portal is archived" + }, + "color": { + "type": "string", + "description": "The color code for the portal" + }, + "config": { + "$ref": "#/components/schemas/portal_config" + }, + "custom_domain": { + "type": "string", + "description": "Custom domain for the portal" + }, + "header_text": { + "type": "string", + "description": "The header text for the portal" + }, + "homepage_link": { + "type": "string", + "description": "Homepage link for the portal" + }, + "name": { + "type": "string", + "description": "Name of the portal" + }, + "slug": { + "type": "string", + "description": "URL slug for the portal" + }, + "page_title": { + "type": "string", + "description": "Page title for the portal" + }, + "account_id": { + "type": "integer", + "description": "ID of the account the portal belongs to" + }, + "inbox": { + "$ref": "#/components/schemas/inbox" + }, + "logo": { + "$ref": "#/components/schemas/portal_logo" + }, + "meta": { + "$ref": "#/components/schemas/portal_meta" + } + } + }, + "category": { + "type": "object", + "properties": { + "id": { + "type": "integer" + }, + "description": { + "type": "string", + "description": "The text content." + }, + "locale": { + "type": "string" + }, + "name": { + "type": "string" + }, + "slug": { + "type": "string" + }, + "position": { + "type": "integer" + }, + "portal_id": { + "type": "integer" + }, + "account_id": { + "type": "integer" + }, + "associated_category_id": { + "type": "integer", + "description": "To associate similar categories to each other, e.g same category of product documentation in different languages" + }, + "parent_category_id": { + "type": "integer", + "description": "To define parent category, e.g product documentation has multiple level features in sales category or in engineering category." + } + } + }, + "article": { + "type": "object", + "properties": { + "id": { + "type": "integer" + }, + "content": { + "type": "string", + "description": "The text content." + }, + "meta": { + "type": "object" + }, + "position": { + "type": "integer" + }, + "status": { + "type": "integer", + "enum": [ + "draft", + "published", + "archived" + ] + }, + "title": { + "type": "string" + }, + "slug": { + "type": "string" + }, + "views": { + "type": "integer" + }, + "portal_id": { + "type": "integer" + }, + "account_id": { + "type": "integer" + }, + "author_id": { + "type": "integer" + }, + "category_id": { + "type": "integer" + }, + "folder_id": { + "type": "integer" + }, + "associated_article_id": { + "type": "integer", + "description": "To associate similar articles to each other, e.g to provide the link for the reference." + } + } + }, + "contact": { + "type": "object", + "properties": { + "payload": { + "type": "array", + "items": { + "type": "object", + "properties": { + "additional_attributes": { + "type": "object", + "description": "The object containing additional attributes related to the contact" + }, + "availability_status": { + "type": "string", + "description": "The availability status of the contact" + }, + "email": { + "type": "string", + "description": "The email address of the contact" + }, + "id": { + "type": "integer", + "description": "The ID of the contact" + }, + "name": { + "type": "string", + "description": "The name of the contact" + }, + "phone_number": { + "type": "string", + "description": "The phone number of the contact" + }, + "blocked": { + "type": "boolean", + "description": "Whether the contact is blocked" + }, + "identifier": { + "type": "string", + "description": "The identifier of the contact" + }, + "thumbnail": { + "type": "string", + "description": "The thumbnail of the contact" + }, + "custom_attributes": { + "type": "object", + "description": "The custom attributes of the contact", + "example": { + "attribute_key": "attribute_value", + "signed_up_at": "dd/mm/yyyy" + } + }, + "last_activity_at": { + "type": "integer", + "description": "The last activity at of the contact" + }, + "created_at": { + "type": "integer", + "description": "The created at of the contact" + }, + "contact_inboxes": { + "type": "array", + "items": { + "$ref": "#/components/schemas/contact_inboxes" + } + } + } + } + } + } + }, + "conversation": { + "type": "object", + "properties": { + "id": { + "type": "number", + "description": "ID of the conversation" + }, + "messages": { + "type": "array", + "items": { + "$ref": "#/components/schemas/message" + } + }, + "account_id": { + "type": "number", + "description": "Account Id" + }, + "uuid": { + "type": "string", + "description": "UUID of the conversation" + }, + "additional_attributes": { + "type": "object", + "description": "The object containing additional attributes related to the conversation" + }, + "agent_last_seen_at": { + "type": "number", + "description": "The last activity at of the agent" + }, + "assignee_last_seen_at": { + "type": "number", + "description": "The last activity at of the assignee" + }, + "can_reply": { + "type": "boolean", + "description": "Whether the conversation can be replied to" + }, + "contact_last_seen_at": { + "type": "number", + "description": "The last activity at of the contact" + }, + "custom_attributes": { + "type": "object", + "description": "The object to save custom attributes for conversation, accepts custom attributes key and value" + }, + "inbox_id": { + "type": "number", + "description": "ID of the inbox" + }, + "labels": { + "type": "array", + "items": { + "type": "string" + }, + "description": "The labels of the conversation" + }, + "muted": { + "type": "boolean", + "description": "Whether the conversation is muted" + }, + "snoozed_until": { + "type": "number", + "description": "The time at which the conversation will be unmuted" + }, + "status": { + "type": "string", + "enum": [ + "open", + "resolved", + "pending" + ], + "description": "The status of the conversation" + }, + "created_at": { + "type": "number", + "description": "The time at which conversation was created" + }, + "updated_at": { + "type": "number", + "description": "The time at which conversation was updated" + }, + "timestamp": { + "type": "string", + "description": "The time at which conversation was created" + }, + "first_reply_created_at": { + "type": "number", + "description": "The time at which the first reply was created" + }, + "unread_count": { + "type": "number", + "description": "The number of unread messages" + }, + "last_non_activity_message": { + "$ref": "#/components/schemas/message" + }, + "last_activity_at": { + "type": "number", + "description": "The last activity at of the conversation" + }, + "priority": { + "type": "string", + "description": "The priority of the conversation" + }, + "waiting_since": { + "type": "number", + "description": "The time at which the conversation was waiting" + }, + "sla_policy_id": { + "type": "number", + "description": "The ID of the SLA policy" + }, + "applied_sla": { + "type": "object", + "description": "The applied SLA" + }, + "sla_events": { + "type": "array", + "items": { + "type": "object", + "description": "SLA event objects" + } + } + } + }, + "message": { + "type": "object", + "properties": { + "id": { + "type": "number", + "description": "The ID of the message" + }, + "content": { + "type": "string", + "description": "The text content of the message" + }, + "account_id": { + "type": "number", + "description": "The ID of the account" + }, + "inbox_id": { + "type": "number", + "description": "The ID of the inbox" + }, + "conversation_id": { + "type": "number", + "description": "The ID of the conversation" + }, + "message_type": { + "type": "integer", + "enum": [ + 0, + 1, + 2 + ], + "description": "The type of the message" + }, + "created_at": { + "type": "integer", + "description": "The time at which message was created" + }, + "updated_at": { + "type": "integer", + "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" + ], + "description": "The status of the message" + }, + "source_id": { + "type": "string", + "description": "The source ID of the message" + }, + "content_type": { + "type": "string", + "enum": [ + "text", + "input_select", + "cards", + "form" + ], + "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" + ], + "description": "The type of the sender" + }, + "sender_id": { + "type": "number", + "description": "The ID of the sender" + }, + "external_source_ids": { + "type": "object", + "description": "The external source IDs of the message" + }, + "additional_attributes": { + "type": "object", + "description": "The additional attributes of the message" + }, + "processed_message_content": { + "type": "string", + "description": "The processed message content" + }, + "sentiment": { + "type": "object", + "description": "The sentiment of the message" + }, + "conversation": { + "type": "object", + "description": "The conversation object" + }, + "attachment": { + "type": "object", + "description": "The file object attached to the image" + }, + "sender": { + "type": "object", + "description": "User/Agent/AgentBot object" + } + } + }, + "user": { + "type": "object", + "properties": { + "id": { + "type": "number" + }, + "access_token": { + "type": "string" + }, + "account_id": { + "type": "number" + }, + "available_name": { + "type": "string" + }, + "avatar_url": { + "type": "string" + }, + "confirmed": { + "type": "boolean" + }, + "display_name": { + "type": "string", + "nullable": true + }, + "message_signature": { + "type": "string", + "nullable": true + }, + "email": { + "type": "string" + }, + "hmac_identifier": { + "type": "string" + }, + "inviter_id": { + "type": "number" + }, + "name": { + "type": "string" + }, + "provider": { + "type": "string" + }, + "pubsub_token": { + "type": "string" + }, + "role": { + "type": "string", + "enum": [ + "agent", + "administrator" + ] + }, + "ui_settings": { + "type": "object" + }, + "uid": { + "type": "string" + }, + "type": { + "type": "string", + "nullable": true + }, + "custom_attributes": { + "type": "object", + "description": "Available for users who are created through platform APIs and has custom attributes associated." + }, + "accounts": { + "type": "array", + "items": { + "type": "object", + "properties": { + "id": { + "type": "number" + }, + "name": { + "type": "string" + }, + "status": { + "type": "string" + }, + "active_at": { + "type": "string", + "format": "date-time" + }, + "role": { + "type": "string", + "enum": [ + "administrator", + "agent" + ] + }, + "permissions": { + "type": "array", + "items": { + "type": "string" + } + }, + "availability": { + "type": "string" + }, + "availability_status": { + "type": "string" + }, + "auto_offline": { + "type": "boolean" + }, + "custom_role_id": { + "type": "number", + "nullable": true + }, + "custom_role": { + "type": "object", + "nullable": true + } + } + } + } + } + }, + "agent": { + "type": "object", + "properties": { + "id": { + "type": "integer" + }, + "account_id": { + "type": "integer" + }, + "availability_status": { + "type": "string", + "enum": [ + "available", + "busy", + "offline" + ], + "description": "The availability status of the agent computed by Chatwoot." + }, + "auto_offline": { + "type": "boolean", + "description": "Whether the availability status of agent is configured to go offline automatically when away." + }, + "confirmed": { + "type": "boolean", + "description": "Whether the agent has confirmed their email address." + }, + "email": { + "type": "string", + "description": "The email of the agent" + }, + "available_name": { + "type": "string", + "description": "The available name of the agent" + }, + "name": { + "type": "string", + "description": "The name of the agent" + }, + "role": { + "type": "string", + "enum": [ + "agent", + "administrator" + ], + "description": "The role of the agent" + }, + "thumbnail": { + "type": "string", + "description": "The thumbnail of the agent" + }, + "custom_role_id": { + "type": "integer", + "description": "The custom role id of the agent" + } + } + }, + "inbox": { + "type": "object", + "properties": { + "id": { + "type": "number", + "description": "ID of the inbox" + }, + "name": { + "type": "string", + "description": "The name of the inbox" + }, + "website_url": { + "type": "string", + "description": "Website URL" + }, + "channel_type": { + "type": "string", + "description": "The type of the inbox" + }, + "avatar_url": { + "type": "string", + "description": "The avatar image of the inbox" + }, + "widget_color": { + "type": "string", + "description": "Widget Color used for customization of the widget" + }, + "website_token": { + "type": "string", + "description": "Website Token" + }, + "enable_auto_assignment": { + "type": "boolean", + "description": "The flag which shows whether Auto Assignment is enabled or not" + }, + "web_widget_script": { + "type": "string", + "description": "Script used to load the website widget" + }, + "welcome_title": { + "type": "string", + "description": "Welcome title to be displayed on the widget" + }, + "welcome_tagline": { + "type": "string", + "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", + "description": "A greeting message when the user starts the conversation" + }, + "channel_id": { + "type": "number", + "description": "ID of the channel this inbox belongs to" + }, + "working_hours_enabled": { + "type": "boolean", + "description": "The flag which shows whether working hours feature is enabled" + }, + "enable_email_collect": { + "type": "boolean", + "description": "The flag to enable collecting email from contacts" + }, + "csat_survey_enabled": { + "type": "boolean", + "description": "The flag to enable CSAT survey" + }, + "auto_assignment_config": { + "type": "object", + "description": "Configuration settings for auto assignment" + }, + "out_of_office_message": { + "type": "string", + "description": "Message to show when agents are out of office" + }, + "working_hours": { + "type": "array", + "description": "Configuration for working hours of the inbox", + "items": { + "type": "object", + "properties": { + "day_of_week": { + "type": "number", + "description": "Day of the week (0-6, where 0 is Sunday)" + }, + "closed_all_day": { + "type": "boolean", + "description": "Whether the inbox is closed for the entire day" + }, + "open_hour": { + "type": "number", + "description": "Hour when inbox opens (0-23)" + }, + "open_minutes": { + "type": "number", + "description": "Minutes of the hour when inbox opens (0-59)" + }, + "close_hour": { + "type": "number", + "description": "Hour when inbox closes (0-23)" + }, + "close_minutes": { + "type": "number", + "description": "Minutes of the hour when inbox closes (0-59)" + }, + "open_all_day": { + "type": "boolean", + "description": "Whether the inbox is open for the entire day" + } + } + } + }, + "timezone": { + "type": "string", + "description": "Timezone configuration for the inbox" + }, + "callback_webhook_url": { + "type": "string", + "description": "Webhook URL for callbacks" + }, + "allow_messages_after_resolved": { + "type": "boolean", + "description": "Whether to allow messages after a conversation is resolved" + }, + "lock_to_single_conversation": { + "type": "boolean", + "description": "Whether to lock a contact to a single conversation" + }, + "sender_name_type": { + "type": "string", + "description": "Type of sender name to display (e.g., friendly)" + }, + "business_name": { + "type": "string", + "description": "Business name associated with the inbox" + }, + "hmac_mandatory": { + "type": "boolean", + "description": "Whether HMAC verification is mandatory" + }, + "selected_feature_flags": { + "type": "object", + "description": "Selected feature flags for the inbox" + }, + "reply_time": { + "type": "string", + "description": "Expected reply time" + }, + "messaging_service_sid": { + "type": "string", + "description": "Messaging service SID for SMS providers" + }, + "phone_number": { + "type": "string", + "description": "Phone number associated with the inbox" + }, + "medium": { + "type": "string", + "description": "Medium of communication (e.g., sms, email)" + }, + "provider": { + "type": "string", + "description": "Provider of the channel" + } + } + }, + "inbox_contact": { + "type": "object", + "properties": { + "id": { + "type": "number", + "description": "ID of the inbox" + }, + "avatar_url": { + "type": "string", + "description": "The avatar image of the inbox" + }, + "channel_id": { + "type": "number", + "description": "The ID of the channel" + }, + "name": { + "type": "string", + "description": "The name of the inbox" + }, + "channel_type": { + "type": "string", + "description": "The type of the inbox" + }, + "provider": { + "type": "string", + "description": "The provider of the inbox" + } + } + }, + "agent_bot": { + "type": "object", + "properties": { + "id": { + "type": "number", + "description": "ID of the agent bot" + }, + "name": { + "type": "string", + "description": "The name of the agent bot" + }, + "description": { + "type": "string", + "description": "The description about the agent bot" + }, + "thumbnail": { + "type": "string", + "description": "The thumbnail of the agent bot" + }, + "outgoing_url": { + "type": "string", + "description": "The webhook URL for the bot" + }, + "bot_type": { + "type": "string", + "description": "The type of the bot" + }, + "bot_config": { + "type": "object", + "description": "The configuration of the bot" + }, + "account_id": { + "type": "number", + "description": "Account ID if it's an account specific bot" + }, + "access_token": { + "type": "string", + "description": "The access token for the bot" + }, + "system_bot": { + "type": "boolean", + "description": "Whether the bot is a system bot" + } + } + }, + "contact_inboxes": { + "type": "object", + "properties": { + "source_id": { + "type": "string", + "description": "Contact Inbox Source Id" + }, + "inbox": { + "$ref": "#/components/schemas/inbox_contact" + } + } + }, + "contactable_inboxes": { + "type": "object", + "properties": { + "source_id": { + "type": "string", + "description": "Contact Inbox Source Id" + }, + "inbox": { + "$ref": "#/components/schemas/inbox" + } + } + }, + "custom_filter": { + "type": "object", + "properties": { + "id": { + "type": "number", + "description": "The ID of the custom filter" + }, + "name": { + "type": "string", + "description": "The name of the custom filter" + }, + "type": { + "type": "string", + "enum": [ + "conversation", + "contact", + "report" + ], + "description": "The description about the custom filter" + }, + "query": { + "type": "object", + "description": "A query that needs to be saved as a custom filter" + }, + "created_at": { + "type": "string", + "format": "date-time", + "description": "The time at which the custom filter was created" + }, + "updated_at": { + "type": "string", + "format": "date-time", + "description": "The time at which the custom filter was updated" + } + } + }, + "webhook": { + "type": "object", + "properties": { + "id": { + "type": "number", + "description": "The ID of the webhook" + }, + "url": { + "type": "string", + "description": "The url to which the events will be send" + }, + "subscriptions": { + "type": "array", + "items": { + "type": "string", + "enum": [ + "conversation_created", + "conversation_status_changed", + "conversation_updated", + "contact_created", + "contact_updated", + "message_created", + "message_updated", + "webwidget_triggered" + ] + }, + "description": "The list of subscribed events" + }, + "account_id": { + "type": "number", + "description": "The id of the account which the webhook object belongs to" + } + } + }, + "account": { + "type": "object", + "properties": { + "id": { + "type": "number", + "description": "Account ID" + }, + "name": { + "type": "string", + "description": "Name of the account" + }, + "role": { + "type": "string", + "enum": [ + "administrator", + "agent" + ], + "description": "The user role in the account" + } + } + }, + "account_user": { + "type": "array", + "description": "Array of account users", + "items": { + "type": "object", + "properties": { + "account_id": { + "type": "integer", + "description": "The ID of the account" + }, + "user_id": { + "type": "integer", + "description": "The ID of the user" + }, + "role": { + "type": "string", + "description": "whether user is an administrator or agent" + } + } + } + }, + "platform_account": { + "type": "object", + "properties": { + "id": { + "type": "number", + "description": "Account ID" + }, + "name": { + "type": "string", + "description": "Name of the account" + } + } + }, + "team": { + "type": "object", + "properties": { + "id": { + "type": "number", + "description": "The ID of the team" + }, + "name": { + "type": "string", + "description": "The name of the team" + }, + "description": { + "type": "string", + "description": "The description about the team" + }, + "allow_auto_assign": { + "type": "boolean", + "description": "If this setting is turned on, the system would automatically assign the conversation to an agent in the team while assigning the conversation to a team" + }, + "account_id": { + "type": "number", + "description": "The ID of the account with the team is a part of" + }, + "is_member": { + "type": "boolean", + "description": "This field shows whether the current user is a part of the team" + } + } + }, + "integrations_app": { + "type": "object", + "properties": { + "id": { + "type": "string", + "description": "The ID of the integration" + }, + "name": { + "type": "string", + "description": "The name of the integration" + }, + "description": { + "type": "string", + "description": "The description about the team" + }, + "hook_type": { + "type": "string", + "description": "Whether the integration is an account or inbox integration" + }, + "enabled": { + "type": "boolean", + "description": "Whether the integration is enabled for the account" + }, + "allow_multiple_hooks": { + "type": "boolean", + "description": "Whether multiple hooks can be created for the integration" + }, + "hooks": { + "type": "array", + "items": { + "type": "object" + }, + "description": "If there are any hooks created for this integration" + } + } + }, + "integrations_hook": { + "type": "object", + "properties": { + "id": { + "type": "string", + "description": "The ID of the integration hook" + }, + "app_id": { + "type": "string", + "description": "The ID of the integration app" + }, + "inbox_id": { + "type": "string", + "description": "Inbox ID if its an Inbox integration" + }, + "account_id": { + "type": "string", + "description": "Account ID of the integration" + }, + "status": { + "type": "boolean", + "description": "Whether the integration hook is enabled for the account" + }, + "hook_type": { + "type": "boolean", + "description": "Whether its an account or inbox integration hook" + }, + "settings": { + "type": "object", + "description": "The associated settings for the integration" + } + } + }, + "public_contact": { + "type": "object", + "properties": { + "id": { + "type": "integer", + "description": "Id of the contact" + }, + "source_id": { + "type": "string", + "description": "The session identifier of the contact" + }, + "name": { + "type": "string", + "description": "Name of the contact" + }, + "email": { + "type": "string", + "description": "Email of the contact" + }, + "pubsub_token": { + "type": "string", + "description": "The token to be used to connect to chatwoot websocket" + } + } + }, + "public_conversation": { + "type": "object", + "properties": { + "id": { + "type": "integer", + "description": "Id of the conversation" + }, + "inbox_id": { + "type": "string", + "description": "The inbox id of the conversation" + }, + "messages": { + "type": "array", + "items": { + "$ref": "#/components/schemas/message" + }, + "description": "Messages in the conversation" + }, + "contact": { + "type": "object", + "description": "The contact information associated to the conversation" + } + } + }, + "public_message": { + "type": "object", + "properties": { + "id": { + "type": "string", + "description": "Id of the message" + }, + "content": { + "type": "string", + "description": "Text content of the message" + }, + "message_type": { + "type": "string", + "description": "Denotes the message type" + }, + "content_type": { + "type": "string", + "description": "Content type of the message" + }, + "content_attributes": { + "type": "string", + "description": "Additional content attributes of the message" + }, + "created_at": { + "type": "string", + "description": "Created at time stamp of the message" + }, + "conversation_id": { + "type": "string", + "description": "Conversation Id of the message" + }, + "attachments": { + "type": "array", + "items": { + "type": "object" + }, + "description": "Attachments if any" + }, + "sender": { + "type": "object", + "description": "Details of the sender" + } + } + }, + "public_inbox": { + "type": "object", + "properties": { + "identifier": { + "type": "string", + "description": "Inbox identifier" + }, + "name": { + "type": "string", + "description": "Name of the inbox" + }, + "timezone": { + "type": "string", + "description": "The timezone defined on the inbox" + }, + "working_hours": { + "type": "array", + "description": "The working hours defined on the inbox", + "items": { + "type": "object", + "properties": { + "day_of_week": { + "type": "integer", + "description": "Day of the week as a number. Sunday -> 0, Saturday -> 6" + }, + "open_all_day": { + "type": "boolean", + "description": "Whether or not the business is open the whole day" + }, + "closed_all_day": { + "type": "boolean", + "description": "Whether or not the business is closed the whole day" + }, + "open_hour": { + "type": "integer", + "description": "Opening hour. Can be null if closed all day" + }, + "open_minutes": { + "type": "integer", + "description": "Opening minute. Can be null if closed all day" + }, + "close_hour": { + "type": "integer", + "description": "Closing hour. Can be null if closed all day" + }, + "close_minutes": { + "type": "integer", + "description": "Closing minute. Can be null if closed all day" + } + } + } + }, + "working_hours_enabled": { + "type": "boolean", + "description": "Whether of not the working hours are enabled on the inbox" + }, + "csat_survey_enabled": { + "type": "boolean", + "description": "Whether of not the Customer Satisfaction survey is enabled on the inbox" + }, + "greeting_enabled": { + "type": "boolean", + "description": "Whether of not the Greeting Message is enabled on the inbox" + }, + "identity_validation_enabled": { + "type": "boolean", + "description": "Whether of not the User Identity Validation is enforced on the inbox" + } + } + }, + "account_create_update_payload": { + "type": "object", + "properties": { + "name": { + "type": "string", + "description": "Name of the account", + "example": "My Account" + }, + "locale": { + "type": "string", + "description": "The locale of the account", + "example": "en" + }, + "domain": { + "type": "string", + "description": "The domain of the account", + "example": "example.com" + }, + "support_email": { + "type": "string", + "description": "The support email of the account", + "example": "support@example.com" + }, + "status": { + "type": "string", + "enum": [ + "active", + "suspended" + ], + "description": "The status of the account", + "example": "active" + }, + "limits": { + "type": "object", + "description": "The limits of the account", + "example": {} + }, + "custom_attributes": { + "type": "object", + "description": "The custom attributes of the account", + "example": {} + } + } + }, + "account_user_create_update_payload": { + "type": "object", + "required": [ + "user_id", + "role" + ], + "properties": { + "user_id": { + "type": "integer", + "description": "The ID of the user", + "example": 1 + }, + "role": { + "type": "string", + "description": "whether user is an administrator or agent", + "example": "administrator" + } + } + }, + "platform_agent_bot_create_update_payload": { + "type": "object", + "properties": { + "name": { + "type": "string", + "description": "The name of the agent bot", + "example": "My Agent Bot" + }, + "description": { + "type": "string", + "description": "The description of the agent bot", + "example": "This is a sample agent bot" + }, + "outgoing_url": { + "type": "string", + "description": "The webhook URL for the bot", + "example": "https://example.com/webhook" + }, + "account_id": { + "type": "integer", + "description": "The account ID to associate the agent bot with", + "example": 1 + }, + "avatar": { + "type": "string", + "format": "binary", + "description": "Send the form data with the avatar image binary or use the avatar_url" + }, + "avatar_url": { + "type": "string", + "description": "The url to a jpeg, png file for the agent bot avatar", + "example": "https://example.com/avatar.png" + } + } + }, + "agent_bot_create_update_payload": { + "type": "object", + "properties": { + "name": { + "type": "string", + "description": "The name of the agent bot", + "example": "My Agent Bot" + }, + "description": { + "type": "string", + "description": "The description of the agent bot", + "example": "This is a sample agent bot" + }, + "outgoing_url": { + "type": "string", + "description": "The webhook URL for the bot", + "example": "https://example.com/webhook" + }, + "avatar": { + "type": "string", + "format": "binary", + "description": "Send the form data with the avatar image binary or use the avatar_url" + }, + "avatar_url": { + "type": "string", + "description": "The url to a jpeg, png file for the agent bot avatar", + "example": "https://example.com/avatar.png" + }, + "bot_type": { + "type": "integer", + "description": "The type of the bot (0 for webhook)", + "example": 0 + }, + "bot_config": { + "type": "object", + "description": "The configuration for the bot", + "example": {} + } + } + }, + "user_create_update_payload": { + "type": "object", + "properties": { + "name": { + "type": "string", + "description": "Name of the user", + "example": "Daniel" + }, + "display_name": { + "type": "string", + "description": "Display name of the user", + "example": "Dan" + }, + "email": { + "type": "string", + "description": "Email of the user", + "example": "daniel@acme.inc" + }, + "password": { + "type": "string", + "description": "Password must contain uppercase, lowercase letters, number and a special character", + "example": "Password2!" + }, + "custom_attributes": { + "type": "object", + "description": "Custom attributes you want to associate with the user", + "example": {} + } + } + }, + "canned_response_create_update_payload": { + "type": "object", + "properties": { + "content": { + "type": "string", + "description": "Message content for canned response", + "example": "Hello, {{contact.name}}! Welcome to our service." + }, + "short_code": { + "type": "string", + "description": "Short Code for quick access of the canned response", + "example": "welcome" + } + } + }, + "custom_attribute_create_update_payload": { + "type": "object", + "properties": { + "attribute_display_name": { + "type": "string", + "description": "Attribute display name", + "example": "Custom Attribute" + }, + "attribute_display_type": { + "type": "integer", + "description": "Attribute display type (text- 0, number- 1, currency- 2, percent- 3, link- 4, date- 5, list- 6, checkbox- 7)", + "example": 0 + }, + "attribute_description": { + "type": "string", + "description": "Attribute description", + "example": "This is a custom attribute" + }, + "attribute_key": { + "type": "string", + "description": "Attribute unique key value", + "example": "custom_attribute" + }, + "attribute_values": { + "type": "array", + "description": "Attribute values", + "items": { + "type": "string" + }, + "example": [ + "value1", + "value2" + ] + }, + "attribute_model": { + "type": "integer", + "description": "Attribute type(conversation_attribute- 0, contact_attribute- 1)", + "example": 0 + }, + "regex_pattern": { + "type": "string", + "description": "Regex pattern (Only applicable for type- text). The regex pattern is used to validate the attribute value(s).", + "example": "^[a-zA-Z0-9]+$" + }, + "regex_cue": { + "type": "string", + "description": "Regex cue message (Only applicable for type- text). The cue message is shown when the regex pattern is not matched.", + "example": "Please enter a valid value" + } + } + }, + "agent_create_payload": { + "type": "object", + "required": [ + "name", + "email", + "role" + ], + "properties": { + "name": { + "type": "string", + "description": "Full Name of the agent", + "example": "John Doe" + }, + "email": { + "type": "string", + "description": "Email of the Agent", + "example": "john.doe@acme.inc" + }, + "role": { + "type": "string", + "enum": [ + "agent", + "administrator" + ], + "description": "Whether its administrator or agent", + "example": "agent" + }, + "availability_status": { + "type": "string", + "enum": [ + "available", + "busy", + "offline" + ], + "description": "The availability setting of the agent.", + "example": "available" + }, + "auto_offline": { + "type": "boolean", + "description": "Whether the availability status of agent is configured to go offline automatically when away.", + "example": true + } + } + }, + "agent_update_payload": { + "type": "object", + "required": [ + "role" + ], + "properties": { + "role": { + "type": "string", + "enum": [ + "agent", + "administrator" + ], + "description": "Whether its administrator or agent", + "example": "agent" + }, + "availability_status": { + "type": "string", + "enum": [ + "available", + "busy", + "offline" + ], + "description": "The availability status of the agent.", + "example": "available" + }, + "auto_offline": { + "type": "boolean", + "description": "Whether the availability status of agent is configured to go offline automatically when away.", + "example": true + } + } + }, + "contact_create_payload": { + "type": "object", + "required": [ + "inbox_id" + ], + "properties": { + "inbox_id": { + "type": "number", + "description": "ID of the inbox to which the contact belongs", + "example": 1 + }, + "name": { + "type": "string", + "description": "name of the contact", + "example": "Alice" + }, + "email": { + "type": "string", + "description": "email of the contact", + "example": "alice@acme.inc" + }, + "blocked": { + "type": "boolean", + "description": "whether the contact is blocked or not", + "example": false + }, + "phone_number": { + "type": "string", + "description": "phone number of the contact", + "example": "+123456789" + }, + "avatar": { + "type": "string", + "format": "binary", + "description": "Send the form data with the avatar image binary or use the avatar_url" + }, + "avatar_url": { + "type": "string", + "description": "The url to a jpeg, png file for the contact avatar", + "example": "https://example.com/avatar.png" + }, + "identifier": { + "type": "string", + "description": "A unique identifier for the contact in external system", + "example": "1234567890" + }, + "additional_attributes": { + "type": "object", + "description": "An object where you can store additional attributes for contact. example {\"type\":\"customer\", \"age\":30}", + "example": { + "type": "customer", + "age": 30 + } + }, + "custom_attributes": { + "type": "object", + "description": "An object where you can store custom attributes for contact. example {\"type\":\"customer\", \"age\":30}, this should have a valid custom attribute definition.", + "example": {} + } + } + }, + "contact_update_payload": { + "type": "object", + "properties": { + "name": { + "type": "string", + "description": "name of the contact", + "example": "Alice" + }, + "email": { + "type": "string", + "description": "email of the contact", + "example": "alice@acme.inc" + }, + "blocked": { + "type": "boolean", + "description": "whether the contact is blocked or not", + "example": false + }, + "phone_number": { + "type": "string", + "description": "phone number of the contact", + "example": "+123456789" + }, + "avatar": { + "type": "string", + "format": "binary", + "description": "Send the form data with the avatar image binary or use the avatar_url" + }, + "avatar_url": { + "type": "string", + "description": "The url to a jpeg, png file for the contact avatar", + "example": "https://example.com/avatar.png" + }, + "identifier": { + "type": "string", + "description": "A unique identifier for the contact in external system", + "example": "1234567890" + }, + "additional_attributes": { + "type": "object", + "description": "An object where you can store additional attributes for contact. example {\"type\":\"customer\", \"age\":30}", + "example": { + "type": "customer", + "age": 30 + } + }, + "custom_attributes": { + "type": "object", + "description": "An object where you can store custom attributes for contact. example {\"type\":\"customer\", \"age\":30}, this should have a valid custom attribute definition.", + "example": {} + } + } + }, + "conversation_create_payload": { + "type": "object", + "required": [ + "source_id", + "inbox_id" + ], + "properties": { + "source_id": { + "type": "string", + "description": "Conversation source id", + "example": "1234567890" + }, + "inbox_id": { + "type": "integer", + "description": "Id of inbox in which the conversation is created Allowed Inbox Types: Website, Phone, Api, Email", + "example": 1 + }, + "contact_id": { + "type": "integer", + "description": "Contact Id for which conversation is created", + "example": 1 + }, + "additional_attributes": { + "type": "object", + "description": "Lets you specify attributes like browser information", + "example": { + "browser": "Chrome", + "browser_version": "89.0.4389.82", + "os": "Windows", + "os_version": "10" + } + }, + "custom_attributes": { + "type": "object", + "description": "The object to save custom attributes for conversation, accepts custom attributes key and value", + "example": { + "attribute_key": "attribute_value", + "priority_conversation_number": 3 + } + }, + "status": { + "type": "string", + "enum": [ + "open", + "resolved", + "pending" + ], + "description": "Specify the conversation whether it's pending, open, closed", + "example": "open" + }, + "assignee_id": { + "type": "integer", + "description": "Agent Id for assigning a conversation to an agent", + "example": 1 + }, + "team_id": { + "type": "integer", + "description": "Team Id for assigning a conversation to a team\\", + "example": 1 + }, + "snoozed_until": { + "type": "string", + "format": "date-time", + "description": "Snoozed until date time", + "example": "2030-07-21T17:32:28Z" + }, + "message": { + "type": "object", + "description": "The initial message to be sent to the conversation", + "required": [ + "content" + ], + "properties": { + "content": { + "type": "string", + "description": "The content of the message", + "example": "Hello, how can I help you?" + }, + "template_params": { + "type": "object", + "description": "The template params for the message in case of whatsapp Channel", + "properties": { + "name": { + "type": "string", + "description": "Name of the template", + "example": "sample_issue_resolution" + }, + "category": { + "type": "string", + "description": "Category of the template", + "example": "UTILITY" + }, + "language": { + "type": "string", + "description": "Language of the template", + "example": "en_US" + }, + "processed_params": { + "type": "object", + "description": "The processed param values for template variables in template", + "example": { + "1": "Chatwoot" + } + } + } + } + } + } + } + }, + "conversation_message_create_payload": { + "type": "object", + "required": [ + "content" + ], + "properties": { + "content": { + "type": "string", + "description": "The content of the message", + "example": "Hello, how can I help you?" + }, + "message_type": { + "type": "string", + "enum": [ + "outgoing", + "incoming" + ], + "description": "The type of the message", + "example": "outgoing" + }, + "private": { + "type": "boolean", + "description": "Flag to identify if it is a private note", + "example": false + }, + "content_type": { + "type": "string", + "enum": [ + "text", + "input_email", + "cards", + "input_select", + "form", + "article" + ], + "description": "Content type of the message", + "example": "text" + }, + "content_attributes": { + "type": "object", + "description": "Attributes based on the content type", + "example": {} + }, + "campaign_id": { + "type": "integer", + "description": "The campaign id to which the message belongs", + "example": 1 + }, + "template_params": { + "type": "object", + "description": "The template params for the message in case of whatsapp Channel", + "properties": { + "name": { + "type": "string", + "description": "Name of the template", + "example": "sample_issue_resolution" + }, + "category": { + "type": "string", + "description": "Category of the template", + "example": "UTILITY" + }, + "language": { + "type": "string", + "description": "Language of the template", + "example": "en_US" + }, + "processed_params": { + "type": "object", + "description": "The processed param values for template variables in template", + "example": { + "1": "Chatwoot" + } + } + } + } + } + }, + "inbox_create_payload": { + "type": "object", + "properties": { + "name": { + "type": "string", + "description": "The name of the inbox", + "example": "Support" + }, + "avatar": { + "type": "string", + "format": "binary", + "description": "Image file for avatar" + }, + "greeting_enabled": { + "type": "boolean", + "description": "Enable greeting message", + "example": true + }, + "greeting_message": { + "type": "string", + "description": "Greeting message to be displayed on the widget", + "example": "Hello, how can I help you?" + }, + "enable_email_collect": { + "type": "boolean", + "description": "Enable email collection", + "example": true + }, + "csat_survey_enabled": { + "type": "boolean", + "description": "Enable CSAT survey", + "example": true + }, + "enable_auto_assignment": { + "type": "boolean", + "description": "Enable Auto Assignment", + "example": true + }, + "working_hours_enabled": { + "type": "boolean", + "description": "Enable working hours", + "example": true + }, + "out_of_office_message": { + "type": "string", + "description": "Out of office message to be displayed on the widget", + "example": "We are currently out of office. Please leave a message and we will get back to you." + }, + "timezone": { + "type": "string", + "description": "Timezone of the inbox", + "example": "America/New_York" + }, + "allow_messages_after_resolved": { + "type": "boolean", + "description": "Allow messages after conversation is resolved", + "example": true + }, + "lock_to_single_conversation": { + "type": "boolean", + "description": "Lock to single conversation", + "example": true + }, + "portal_id": { + "type": "integer", + "description": "Id of the help center portal to attach to the inbox", + "example": 1 + }, + "sender_name_type": { + "type": "string", + "description": "Sender name type for the inbox", + "enum": [ + "friendly", + "professional" + ], + "example": "friendly" + }, + "business_name": { + "type": "string", + "description": "Business name for the inbox", + "example": "My Business" + }, + "channel": { + "type": "object", + "properties": { + "type": { + "type": "string", + "description": "Type of the channel", + "enum": [ + "web_widget", + "api", + "email", + "line", + "telegram", + "whatsapp", + "sms" + ], + "example": "web_widget" + }, + "website_url": { + "type": "string", + "description": "URL at which the widget will be loaded", + "example": "https://example.com" + }, + "welcome_title": { + "type": "string", + "description": "Welcome title to be displayed on the widget", + "example": "Welcome to our support" + }, + "welcome_tagline": { + "type": "string", + "description": "Welcome tagline to be displayed on the widget", + "example": "We are here to help you" + }, + "widget_color": { + "type": "string", + "description": "A Hex-color string used to customize the widget", + "example": "#FF5733" + } + } + } + } + }, + "inbox_update_payload": { + "type": "object", + "properties": { + "name": { + "type": "string", + "description": "The name of the inbox", + "example": "Support" + }, + "avatar": { + "type": "string", + "format": "binary", + "description": "Image file for avatar" + }, + "greeting_enabled": { + "type": "boolean", + "description": "Enable greeting message", + "example": true + }, + "greeting_message": { + "type": "string", + "description": "Greeting message to be displayed on the widget", + "example": "Hello, how can I help you?" + }, + "enable_email_collect": { + "type": "boolean", + "description": "Enable email collection", + "example": true + }, + "csat_survey_enabled": { + "type": "boolean", + "description": "Enable CSAT survey", + "example": true + }, + "enable_auto_assignment": { + "type": "boolean", + "description": "Enable Auto Assignment", + "example": true + }, + "working_hours_enabled": { + "type": "boolean", + "description": "Enable working hours", + "example": true + }, + "out_of_office_message": { + "type": "string", + "description": "Out of office message to be displayed on the widget", + "example": "We are currently out of office. Please leave a message and we will get back to you." + }, + "timezone": { + "type": "string", + "description": "Timezone of the inbox", + "example": "America/New_York" + }, + "allow_messages_after_resolved": { + "type": "boolean", + "description": "Allow messages after conversation is resolved", + "example": true + }, + "lock_to_single_conversation": { + "type": "boolean", + "description": "Lock to single conversation", + "example": true + }, + "portal_id": { + "type": "integer", + "description": "Id of the help center portal to attach to the inbox", + "example": 1 + }, + "sender_name_type": { + "type": "string", + "description": "Sender name type for the inbox", + "enum": [ + "friendly", + "professional" + ], + "example": "friendly" + }, + "business_name": { + "type": "string", + "description": "Business name for the inbox", + "example": "My Business" + }, + "channel": { + "type": "object", + "properties": { + "website_url": { + "type": "string", + "description": "URL at which the widget will be loaded", + "example": "https://example.com" + }, + "welcome_title": { + "type": "string", + "description": "Welcome title to be displayed on the widget", + "example": "Welcome to our support" + }, + "welcome_tagline": { + "type": "string", + "description": "Welcome tagline to be displayed on the widget", + "example": "We are here to help you" + }, + "widget_color": { + "type": "string", + "description": "A Hex-color string used to customize the widget", + "example": "#FF5733" + } + } + } + } + }, + "team_create_update_payload": { + "type": "object", + "properties": { + "name": { + "type": "string", + "description": "The name of the team", + "example": "Support Team" + }, + "description": { + "type": "string", + "description": "The description of the team", + "example": "This is a team of support agents" + }, + "allow_auto_assign": { + "type": "boolean", + "description": "If this setting is turned on, the system would automatically assign the conversation to an agent in the team while assigning the conversation to a team", + "example": true + } + } + }, + "custom_filter_create_update_payload": { + "type": "object", + "properties": { + "name": { + "type": "string", + "description": "The name of the custom filter", + "example": "My Custom Filter" + }, + "type": { + "type": "string", + "enum": [ + "conversation", + "contact", + "report" + ], + "description": "The description about the custom filter", + "example": "conversation" + }, + "query": { + "type": "object", + "description": "A query that needs to be saved as a custom filter", + "example": {} + } + } + }, + "webhook_create_update_payload": { + "type": "object", + "properties": { + "url": { + "type": "string", + "description": "The url where the events should be sent", + "example": "https://example.com/webhook" + }, + "subscriptions": { + "type": "array", + "items": { + "type": "string", + "enum": [ + "conversation_created", + "conversation_status_changed", + "conversation_updated", + "message_created", + "message_updated", + "contact_created", + "contact_updated", + "webwidget_triggered" + ] + }, + "description": "The events you want to subscribe to.", + "example": [ + "conversation_created", + "conversation_status_changed" + ] + } + } + }, + "integrations_hook_create_payload": { + "type": "object", + "properties": { + "app_id": { + "type": "integer", + "description": "The ID of app for which integration hook is being created", + "example": 1 + }, + "inbox_id": { + "type": "integer", + "description": "The inbox ID, if the hook is an inbox hook", + "example": 1 + }, + "status": { + "type": "integer", + "description": "The status of the integration (0 for inactive, 1 for active)", + "example": 1 + }, + "settings": { + "type": "object", + "description": "The settings required by the integration", + "example": {} + } + } + }, + "integrations_hook_update_payload": { + "type": "object", + "properties": { + "status": { + "type": "integer", + "description": "The status of the integration (0 for inactive, 1 for active)", + "example": 1 + }, + "settings": { + "type": "object", + "description": "The settings required by the integration", + "example": {} + } + } + }, + "automation_rule_create_update_payload": { + "type": "object", + "properties": { + "name": { + "type": "string", + "description": "Rule name", + "example": "Add label on message create event" + }, + "description": { + "type": "string", + "description": "The description about the automation and actions", + "example": "Add label support and sales on message create event if incoming message content contains text help" + }, + "event_name": { + "type": "string", + "enum": [ + "conversation_created", + "conversation_updated", + "message_created" + ], + "example": "message_created", + "description": "The event when you want to execute the automation actions" + }, + "active": { + "type": "boolean", + "description": "Enable/disable automation rule" + }, + "actions": { + "type": "array", + "description": "Array of actions which you want to perform when condition matches, e.g add label support if message contains content help.", + "items": { + "type": "object", + "example": { + "action_name": "add_label", + "action_params": [ + "support" + ] + } + } + }, + "conditions": { + "type": "array", + "description": "Array of conditions on which conversation filter would work, e.g message content contains text help.", + "items": { + "type": "object", + "example": { + "attribute_key": "content", + "filter_operator": "contains", + "query_operator": "OR", + "values": [ + "help" + ] + } + } + } + } + }, + "portal_create_update_payload": { + "type": "object", + "properties": { + "color": { + "type": "string", + "description": "Header color for help-center in hex format", + "example": "#FFFFFF" + }, + "custom_domain": { + "type": "string", + "description": "Custom domain to display help center.", + "example": "chatwoot.help" + }, + "header_text": { + "type": "string", + "description": "Help center header", + "example": "Handbook" + }, + "homepage_link": { + "type": "string", + "description": "link to main dashboard", + "example": "https://www.chatwoot.com/" + }, + "name": { + "type": "string", + "description": "Name for the portal", + "example": "Handbook" + }, + "page_title": { + "type": "string", + "description": "Page title for the portal", + "example": "Handbook" + }, + "slug": { + "type": "string", + "description": "Slug for the portal to display in link", + "example": "handbook" + }, + "archived": { + "type": "boolean", + "description": "Status to check if portal is live", + "example": false + }, + "config": { + "type": "object", + "description": "Configuration about supporting locales", + "example": { + "allowed_locales": [ + "en", + "es" + ], + "default_locale": "en" + } + } + } + }, + "category_create_update_payload": { + "type": "object", + "properties": { + "name": { + "type": "string", + "description": "The name of the category", + "example": "Category Name" + }, + "description": { + "type": "string", + "description": "A description for the category", + "example": "Category description" + }, + "position": { + "type": "integer", + "description": "Category position in the portal list to sort", + "example": 1 + }, + "slug": { + "type": "string", + "description": "The category slug used in the URL", + "example": "category-name" + }, + "locale": { + "type": "string", + "description": "The locale of the category", + "example": "en" + }, + "icon": { + "type": "string", + "description": "The icon of the category as a string (emoji)", + "example": "📚" + }, + "parent_category_id": { + "type": "integer", + "description": "To define parent category, e.g product documentation has multiple level features in sales category or in engineering category.", + "example": 1 + }, + "associated_category_id": { + "type": "integer", + "description": "To associate similar categories to each other, e.g same category of product documentation in different languages", + "example": 2 + } + } + }, + "article_create_update_payload": { + "type": "object", + "properties": { + "title": { + "type": "string", + "description": "The title of the article", + "example": "Article Title" + }, + "slug": { + "type": "string", + "description": "The slug of the article", + "example": "article-title" + }, + "position": { + "type": "integer", + "description": "article position in category", + "example": 1 + }, + "content": { + "type": "string", + "description": "The text content.", + "example": "This is the content of the article" + }, + "description": { + "type": "string", + "description": "The description of the article", + "example": "This is the description of the article" + }, + "category_id": { + "type": "integer", + "description": "The category id of the article", + "example": 1 + }, + "author_id": { + "type": "integer", + "description": "The author agent id of the article", + "example": 1 + }, + "associated_article_id": { + "type": "integer", + "description": "To associate similar articles to each other, e.g to provide the link for the reference.", + "example": 2 + }, + "status": { + "type": "integer", + "description": "The status of the article. 0 for draft, 1 for published, 2 for archived", + "example": 1 + }, + "locale": { + "type": "string", + "description": "The locale of the article", + "example": "en" + }, + "meta": { + "type": "object", + "description": "Use for search", + "example": { + "tags": [ + "article_name" + ], + "title": "article title", + "description": "article description" + } + } + } + }, + "public_contact_create_update_payload": { + "type": "object", + "properties": { + "identifier": { + "type": "string", + "description": "External identifier of the contact", + "example": "1234567890" + }, + "identifier_hash": { + "type": "string", + "description": "Identifier hash prepared for HMAC authentication", + "example": "e93275d4eba0e5679ad55f5360af00444e2a888df9b0afa3e8b691c3173725f9" + }, + "email": { + "type": "string", + "description": "Email of the contact", + "example": "alice@acme.inc" + }, + "name": { + "type": "string", + "description": "Name of the contact", + "example": "Alice" + }, + "phone_number": { + "type": "string", + "description": "Phone number of the contact", + "example": "+123456789" + }, + "avatar": { + "type": "string", + "format": "binary", + "description": "Send the form data with the avatar image binary or use the avatar_url" + }, + "custom_attributes": { + "type": "object", + "description": "Custom attributes of the customer", + "example": {} + } + } + }, + "public_message_create_payload": { + "type": "object", + "properties": { + "content": { + "type": "string", + "description": "Content for the message", + "example": "Hello, how can I help you?" + }, + "echo_id": { + "type": "string", + "description": "Temporary identifier which will be passed back via websockets", + "example": "1234567890" + } + } + }, + "public_message_update_payload": { + "type": "object", + "properties": { + "submitted_values": { + "type": "object", + "description": "Replies to the Bot Message Types", + "properties": { + "name": { + "type": "string", + "description": "The name of the submiitted value", + "example": "My Name" + }, + "title": { + "type": "string", + "description": "The title of the submitted value", + "example": "My Title" + }, + "value": { + "type": "string", + "description": "The value of the submitted value", + "example": "value" + }, + "csat_survey_response": { + "type": "object", + "description": "The CSAT survey response", + "properties": { + "feedback_message": { + "type": "string", + "description": "The feedback message of the CSAT survey response", + "example": "Great service!" + }, + "rating": { + "type": "integer", + "description": "The rating of the CSAT survey response", + "example": 5 + } + } + } + } + } + } + }, + "public_conversation_create_payload": { + "type": "object", + "properties": { + "custom_attributes": { + "type": "object", + "description": "Custom attributes of the conversation", + "example": {} + } + } + }, + "extended_contact": { + "allOf": [ + { + "$ref": "#/components/schemas/contact" + }, + { + "type": "object", + "properties": { + "id": { + "type": "number", + "description": "Id of the user" + }, + "availability_status": { + "type": "string", + "enum": [ + "online", + "offline" + ], + "description": "Availability status of the user" + } + } + } + ] + }, + "contact_base": { + "allOf": [ + { + "$ref": "#/components/schemas/generic_id" + }, + { + "$ref": "#/components/schemas/contact" + } + ] + }, + "contact_list": { + "type": "array", + "description": "array of contacts", + "items": { + "allOf": [ + { + "$ref": "#/components/schemas/contact" + } + ] + } + }, + "contact_conversations": { + "type": "array", + "description": "array of conversations", + "items": { + "allOf": [ + { + "$ref": "#/components/schemas/conversation" + }, + { + "type": "object", + "properties": { + "meta": { + "type": "object", + "properties": { + "sender": { + "type": "object", + "properties": { + "additional_attributes": { + "type": "object", + "description": "The additional attributes of the sender" + }, + "availability_status": { + "type": "string", + "description": "The availability status of the sender" + }, + "email": { + "type": "string", + "description": "The email of the sender" + }, + "id": { + "type": "number", + "description": "ID fo the sender" + }, + "name": { + "type": "string", + "description": "The name of the sender" + }, + "phone_number": { + "type": "string", + "description": "The phone number of the sender" + }, + "blocked": { + "type": "boolean", + "description": "Whether the sender is blocked" + }, + "identifier": { + "type": "string", + "description": "The identifier of the sender" + }, + "thumbnail": { + "type": "string", + "description": "Avatar URL of the contact" + }, + "custom_attributes": { + "type": "object", + "description": "The custom attributes of the sender" + }, + "last_activity_at": { + "type": "number", + "description": "The last activity at of the sender" + }, + "created_at": { + "type": "number", + "description": "The created at of the sender" + } + } + }, + "channel": { + "type": "string", + "description": "Channel Type" + }, + "assignee": { + "$ref": "#/components/schemas/user" + }, + "hmac_verified": { + "type": "boolean", + "description": "Whether the hmac is verified" + } + } + } + } + }, + { + "type": "object", + "properties": { + "display_id": { + "type": "number" + } + } + } + ] + } + }, + "contact_labels": { + "type": "object", + "properties": { + "payload": { + "type": "array", + "description": "Array of labels", + "items": { + "type": "string" + } + } + } + }, + "conversation_list": { + "type": "object", + "properties": { + "data": { + "type": "object", + "properties": { + "meta": { + "type": "object", + "properties": { + "mine_count": { + "type": "number" + }, + "unassigned_count": { + "type": "number" + }, + "assigned_count": { + "type": "number" + }, + "all_count": { + "type": "number" + } + } + }, + "payload": { + "type": "array", + "description": "array of conversations", + "items": { + "allOf": [ + { + "$ref": "#/components/schemas/generic_id" + }, + { + "$ref": "#/components/schemas/conversation" + }, + { + "type": "object", + "properties": { + "meta": { + "type": "object", + "properties": { + "sender": { + "type": "object", + "properties": { + "additional_attributes": { + "type": "object", + "description": "The additional attributes of the sender" + }, + "availability_status": { + "type": "string", + "description": "The availability status of the sender" + }, + "email": { + "type": "string", + "description": "The email of the sender" + }, + "id": { + "type": "number", + "description": "ID fo the sender" + }, + "name": { + "type": "string", + "description": "The name of the sender" + }, + "phone_number": { + "type": "string", + "description": "The phone number of the sender" + }, + "blocked": { + "type": "boolean", + "description": "Whether the sender is blocked" + }, + "identifier": { + "type": "string", + "description": "The identifier of the sender" + }, + "thumbnail": { + "type": "string", + "description": "Avatar URL of the contact" + }, + "custom_attributes": { + "type": "object", + "description": "The custom attributes of the sender" + }, + "last_activity_at": { + "type": "number", + "description": "The last activity at of the sender" + }, + "created_at": { + "type": "number", + "description": "The created at of the sender" + } + } + }, + "channel": { + "type": "string", + "description": "Channel Type" + }, + "assignee": { + "$ref": "#/components/schemas/user" + }, + "hmac_verified": { + "type": "boolean", + "description": "Whether the hmac is verified" + } + } + } + } + } + ] + } + } + } + } + } + }, + "conversation_show": { + "type": "object", + "allOf": [ + { + "$ref": "#/components/schemas/conversation" + }, + { + "type": "object", + "properties": { + "meta": { + "type": "object", + "properties": { + "sender": { + "type": "object", + "properties": { + "additional_attributes": { + "type": "object", + "description": "The additional attributes of the sender" + }, + "availability_status": { + "type": "string", + "description": "The availability status of the sender" + }, + "email": { + "type": "string", + "description": "The email of the sender" + }, + "id": { + "type": "number", + "description": "ID fo the sender" + }, + "name": { + "type": "string", + "description": "The name of the sender" + }, + "phone_number": { + "type": "string", + "description": "The phone number of the sender" + }, + "blocked": { + "type": "boolean", + "description": "Whether the sender is blocked" + }, + "identifier": { + "type": "string", + "description": "The identifier of the sender" + }, + "thumbnail": { + "type": "string", + "description": "Avatar URL of the contact" + }, + "custom_attributes": { + "type": "object", + "description": "The custom attributes of the sender" + }, + "last_activity_at": { + "type": "number", + "description": "The last activity at of the sender" + }, + "created_at": { + "type": "number", + "description": "The created at of the sender" + } + } + }, + "channel": { + "type": "string", + "description": "Channel Type" + }, + "assignee": { + "$ref": "#/components/schemas/user" + }, + "hmac_verified": { + "type": "boolean", + "description": "Whether the hmac is verified" + } + } + } + } + } + ] + }, + "conversation_status_toggle": { + "type": "object", + "properties": { + "meta": { + "type": "object" + }, + "payload": { + "type": "object", + "properties": { + "success": { + "type": "boolean" + }, + "current_status": { + "type": "string", + "enum": [ + "open", + "resolved" + ] + }, + "conversation_id": { + "type": "number" + } + } + } + } + }, + "conversation_labels": { + "type": "object", + "properties": { + "payload": { + "type": "array", + "description": "Array of labels", + "items": { + "type": "string" + } + } + } + }, + "account_summary": { + "type": "object", + "properties": { + "avg_first_response_time": { + "type": "string" + }, + "avg_resolution_time": { + "type": "string" + }, + "conversations_count": { + "type": "number" + }, + "incoming_messages_count": { + "type": "number" + }, + "outgoing_messages_count": { + "type": "number" + }, + "resolutions_count": { + "type": "number" + }, + "previous": { + "type": "object", + "properties": { + "avg_first_response_time": { + "type": "string" + }, + "avg_resolution_time": { + "type": "string" + }, + "conversations_count": { + "type": "number" + }, + "incoming_messages_count": { + "type": "number" + }, + "outgoing_messages_count": { + "type": "number" + }, + "resolutions_count": { + "type": "number" + } + } + } + } + }, + "agent_conversation_metrics": { + "type": "object", + "properties": { + "id": { + "type": "number" + }, + "name": { + "type": "string" + }, + "email": { + "type": "string" + }, + "thumbnail": { + "type": "string" + }, + "availability": { + "type": "string" + }, + "metric": { + "type": "object", + "properties": { + "open": { + "type": "number" + }, + "unattended": { + "type": "number" + } + } + } + } + }, + "contact_detail": { + "type": "object", + "properties": { + "additional_attributes": { + "type": "object", + "description": "The object containing additional attributes related to the contact", + "properties": { + "city": { + "type": "string", + "description": "City of the contact" + }, + "country": { + "type": "string", + "description": "Country of the contact" + }, + "country_code": { + "type": "string", + "description": "Country code of the contact" + }, + "created_at_ip": { + "type": "string", + "description": "IP address when the contact was created" + } + } + }, + "custom_attributes": { + "type": "object", + "description": "The custom attributes of the contact" + }, + "email": { + "type": "string", + "description": "The email address of the contact" + }, + "id": { + "type": "integer", + "description": "The ID of the contact" + }, + "identifier": { + "type": "string", + "description": "The identifier of the contact", + "nullable": true + }, + "name": { + "type": "string", + "description": "The name of the contact" + }, + "phone_number": { + "type": "string", + "description": "The phone number of the contact", + "nullable": true + }, + "thumbnail": { + "type": "string", + "description": "The thumbnail of the contact" + }, + "blocked": { + "type": "boolean", + "description": "Whether the contact is blocked" + }, + "type": { + "type": "string", + "description": "The type of entity", + "enum": [ + "contact" + ] + } + } + }, + "message_detailed": { + "type": "object", + "properties": { + "id": { + "type": "number", + "description": "The ID of the message" + }, + "content": { + "type": "string", + "description": "The text content of the message" + }, + "inbox_id": { + "type": "number", + "description": "The ID of the inbox" + }, + "conversation_id": { + "type": "number", + "description": "The ID of the conversation" + }, + "message_type": { + "type": "integer", + "enum": [ + 0, + 1, + 2, + 3 + ], + "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" + ], + "description": "The type of the message content" + }, + "status": { + "type": "string", + "enum": [ + "sent", + "delivered", + "read", + "failed" + ], + "description": "The status of the message" + }, + "content_attributes": { + "type": "object", + "description": "The content attributes for each content_type", + "properties": { + "in_reply_to": { + "type": "string", + "description": "ID of the message this is replying to", + "nullable": true + } + } + }, + "created_at": { + "type": "integer", + "description": "The timestamp when message was created" + }, + "private": { + "type": "boolean", + "description": "The flag which shows whether the message is private or not" + }, + "source_id": { + "type": "string", + "description": "The source ID of the message", + "nullable": true + }, + "sender": { + "$ref": "#/components/schemas/contact_detail" + } + } + }, + "conversation_meta": { + "type": "object", + "properties": { + "labels": { + "type": "array", + "items": { + "type": "string" + }, + "description": "Labels associated with the conversation" + }, + "additional_attributes": { + "type": "object", + "properties": { + "browser": { + "type": "object", + "properties": { + "device_name": { + "type": "string", + "description": "Name of the device" + }, + "browser_name": { + "type": "string", + "description": "Name of the browser" + }, + "platform_name": { + "type": "string", + "description": "Name of the platform" + }, + "browser_version": { + "type": "string", + "description": "Version of the browser" + }, + "platform_version": { + "type": "string", + "description": "Version of the platform" + } + } + }, + "referer": { + "type": "string", + "description": "Referrer URL" + }, + "initiated_at": { + "type": "object", + "properties": { + "timestamp": { + "type": "string", + "description": "Timestamp when the conversation was initiated" + } + } + }, + "browser_language": { + "type": "string", + "description": "Browser language setting" + }, + "conversation_language": { + "type": "string", + "description": "Conversation language" + } + }, + "description": "Additional attributes of the conversation" + }, + "contact": { + "$ref": "#/components/schemas/contact_detail" + }, + "agent_last_seen_at": { + "type": "string", + "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 + } + } + }, + "conversation_messages": { + "type": "object", + "properties": { + "meta": { + "$ref": "#/components/schemas/conversation_meta" + }, + "payload": { + "type": "array", + "items": { + "$ref": "#/components/schemas/message_detailed" + }, + "description": "List of messages in the conversation" + } + } + }, + "contact_meta": { + "type": "object", + "properties": { + "count": { + "type": "integer", + "description": "Total number of contacts" + }, + "current_page": { + "type": "string", + "description": "Current page number" + } + } + }, + "contact_inbox": { + "type": "object", + "properties": { + "source_id": { + "type": "string", + "description": "Source identifier for the contact inbox" + }, + "inbox": { + "type": "object", + "properties": { + "id": { + "type": "integer", + "description": "ID of the inbox" + }, + "avatar_url": { + "type": "string", + "description": "URL for the inbox avatar" + }, + "channel_id": { + "type": "integer", + "description": "ID of the channel" + }, + "name": { + "type": "string", + "description": "Name of the inbox" + }, + "channel_type": { + "type": "string", + "description": "Type of channel" + }, + "provider": { + "type": "string", + "description": "Provider of the inbox", + "nullable": true + } + } + } + } + }, + "contact_list_item": { + "type": "object", + "properties": { + "additional_attributes": { + "type": "object", + "description": "The object containing additional attributes related to the contact", + "properties": { + "city": { + "type": "string", + "description": "City of the contact" + }, + "country": { + "type": "string", + "description": "Country of the contact" + }, + "country_code": { + "type": "string", + "description": "Country code of the contact" + }, + "created_at_ip": { + "type": "string", + "description": "IP address when the contact was created" + } + } + }, + "availability_status": { + "type": "string", + "description": "Availability status of the contact", + "enum": [ + "online", + "offline" + ] + }, + "email": { + "type": "string", + "description": "The email address of the contact", + "nullable": true + }, + "id": { + "type": "integer", + "description": "The ID of the contact" + }, + "name": { + "type": "string", + "description": "The name of the contact" + }, + "phone_number": { + "type": "string", + "description": "The phone number of the contact", + "nullable": true + }, + "blocked": { + "type": "boolean", + "description": "Whether the contact is blocked" + }, + "identifier": { + "type": "string", + "description": "The identifier of the contact", + "nullable": true + }, + "thumbnail": { + "type": "string", + "description": "The thumbnail of the contact" + }, + "custom_attributes": { + "type": "object", + "description": "The custom attributes of the contact" + }, + "last_activity_at": { + "type": "integer", + "description": "Timestamp of last activity", + "nullable": true + }, + "created_at": { + "type": "integer", + "description": "Timestamp when contact was created" + }, + "contact_inboxes": { + "type": "array", + "description": "List of inboxes associated with this contact", + "items": { + "$ref": "#/components/schemas/contact_inbox" + } + } + } + }, + "contacts_list_response": { + "type": "object", + "properties": { + "meta": { + "$ref": "#/components/schemas/contact_meta" + }, + "payload": { + "type": "array", + "items": { + "$ref": "#/components/schemas/contact_list_item" + }, + "description": "List of contacts" + } + } + }, + "contact_show_response": { + "type": "object", + "properties": { + "payload": { + "$ref": "#/components/schemas/contact_list_item" + } + } + }, + "contact_conversation_message": { + "type": "object", + "properties": { + "id": { + "type": "integer", + "description": "ID of the message" + }, + "content": { + "type": "string", + "description": "Content of the message" + }, + "account_id": { + "type": "integer", + "description": "ID of the account" + }, + "inbox_id": { + "type": "integer", + "description": "ID of the inbox" + }, + "conversation_id": { + "type": "integer", + "description": "ID of the conversation" + }, + "message_type": { + "type": "integer", + "description": "Type of the message" + }, + "created_at": { + "type": "integer", + "description": "Timestamp when message was created" + }, + "updated_at": { + "type": "string", + "description": "Formatted datetime when message was updated" + }, + "private": { + "type": "boolean", + "description": "Whether the message is private" + }, + "status": { + "type": "string", + "description": "Status of the message" + }, + "source_id": { + "type": "string", + "description": "Source ID of the message", + "nullable": true + }, + "content_type": { + "type": "string", + "description": "Type of the content" + }, + "content_attributes": { + "type": "object", + "description": "Attributes of the content" + }, + "sender_type": { + "type": "string", + "description": "Type of the sender", + "nullable": true + }, + "sender_id": { + "type": "integer", + "description": "ID of the sender", + "nullable": true + }, + "external_source_ids": { + "type": "object", + "description": "External source IDs" + }, + "additional_attributes": { + "type": "object", + "description": "Additional attributes of the message" + }, + "processed_message_content": { + "type": "string", + "description": "Processed message content", + "nullable": true + }, + "sentiment": { + "type": "object", + "description": "Sentiment analysis of the message" + }, + "conversation": { + "type": "object", + "description": "Conversation details", + "properties": { + "assignee_id": { + "type": "integer", + "description": "ID of the assignee", + "nullable": true + }, + "unread_count": { + "type": "integer", + "description": "Count of unread messages" + }, + "last_activity_at": { + "type": "integer", + "description": "Timestamp of last activity" + }, + "contact_inbox": { + "type": "object", + "description": "Contact inbox details", + "properties": { + "source_id": { + "type": "string", + "description": "Source ID of the contact inbox" + } + } + } + } + }, + "sender": { + "type": "object", + "description": "Details of the sender", + "properties": { + "id": { + "type": "integer", + "description": "ID of the sender" + }, + "name": { + "type": "string", + "description": "Name of the sender" + }, + "available_name": { + "type": "string", + "description": "Available name of the sender" + }, + "avatar_url": { + "type": "string", + "description": "URL of the sender's avatar" + }, + "type": { + "type": "string", + "description": "Type of the sender" + }, + "availability_status": { + "type": "string", + "description": "Availability status of the sender" + }, + "thumbnail": { + "type": "string", + "description": "Thumbnail URL of the sender" + } + } + } + } + }, + "contact_conversations_response": { + "type": "object", + "properties": { + "payload": { + "type": "array", + "items": { + "allOf": [ + { + "$ref": "#/components/schemas/conversation" + }, + { + "type": "object", + "properties": { + "meta": { + "type": "object", + "properties": { + "sender": { + "type": "object", + "properties": { + "additional_attributes": { + "type": "object", + "description": "The additional attributes of the sender" + }, + "availability_status": { + "type": "string", + "description": "The availability status of the sender" + }, + "email": { + "type": "string", + "description": "The email of the sender" + }, + "id": { + "type": "number", + "description": "ID fo the sender" + }, + "name": { + "type": "string", + "description": "The name of the sender" + }, + "phone_number": { + "type": "string", + "description": "The phone number of the sender" + }, + "blocked": { + "type": "boolean", + "description": "Whether the sender is blocked" + }, + "identifier": { + "type": "string", + "description": "The identifier of the sender" + }, + "thumbnail": { + "type": "string", + "description": "Avatar URL of the contact" + }, + "custom_attributes": { + "type": "object", + "description": "The custom attributes of the sender" + }, + "last_activity_at": { + "type": "number", + "description": "The last activity at of the sender" + }, + "created_at": { + "type": "number", + "description": "The created at of the sender" + } + } + }, + "channel": { + "type": "string", + "description": "Channel Type" + }, + "assignee": { + "$ref": "#/components/schemas/user" + }, + "hmac_verified": { + "type": "boolean", + "description": "Whether the hmac is verified" + } + } + } + } + } + ] + }, + "description": "List of conversations for the contact" + } + } + }, + "contactable_inboxes_response": { + "type": "object", + "properties": { + "payload": { + "type": "array", + "items": { + "$ref": "#/components/schemas/contact_inbox" + }, + "description": "List of contactable inboxes for the contact" + } + } + } + }, + "parameters": { + "account_id": { + "in": "path", + "name": "account_id", + "schema": { + "type": "integer" + }, + "required": true, + "description": "The numeric ID of the account" + }, + "agent_bot_id": { + "in": "path", + "name": "id", + "schema": { + "type": "integer" + }, + "required": true, + "description": "The ID of the agentbot to be updated" + }, + "team_id": { + "in": "path", + "name": "team_id", + "schema": { + "type": "integer" + }, + "required": true, + "description": "The ID of the team to be updated" + }, + "inbox_id": { + "in": "path", + "name": "inbox_id", + "schema": { + "type": "integer" + }, + "required": true, + "description": "The ID of the Inbox" + }, + "hook_id": { + "in": "path", + "name": "hook_id", + "schema": { + "type": "integer" + }, + "required": true, + "description": "The numeric ID of the integration hook" + }, + "source_id": { + "in": "path", + "name": "source_id", + "required": true, + "schema": { + "type": "string" + }, + "description": "Id of the session for which the conversation is created.\n\n\n\n Source Ids can be obtained through contactable inboxes API or via generated.Website: Chatwoot generated string which can be obtained from webhook events. Phone Channels(Twilio): Phone number in e164 format Email Channels: Contact Email address API Channel: Any Random String" + }, + "contact_sort_param": { + "in": "query", + "name": "sort", + "schema": { + "type": "string", + "enum": [ + "name", + "email", + "phone_number", + "last_activity_at", + "-name", + "-email", + "-phone_number", + "-last_activity_at" + ] + }, + "required": false, + "description": "The attribute by which list should be sorted" + }, + "conversation_id": { + "in": "path", + "name": "conversation_id", + "schema": { + "type": "integer" + }, + "required": true, + "description": "The numeric ID of the conversation" + }, + "conversation_uuid": { + "in": "path", + "name": "conversation_uuid", + "schema": { + "type": "integer" + }, + "required": true, + "description": "The uuid of the conversation" + }, + "custom_filter_id": { + "in": "path", + "name": "custom_filter_id", + "schema": { + "type": "integer" + }, + "required": true, + "description": "The numeric ID of the custom filter" + }, + "webhook_id": { + "in": "path", + "name": "webhook_id", + "schema": { + "type": "integer" + }, + "required": true, + "description": "The numeric ID of the webhook" + }, + "message_id": { + "in": "path", + "name": "message_id", + "schema": { + "type": "integer" + }, + "required": true, + "description": "The numeric ID of the message" + }, + "page": { + "in": "query", + "name": "page", + "schema": { + "type": "integer", + "default": 1 + }, + "required": false, + "description": "The page parameter" + }, + "platform_user_id": { + "in": "path", + "name": "id", + "schema": { + "type": "integer" + }, + "required": true, + "description": "The numeric ID of the user on the platform" + }, + "report_type": { + "in": "query", + "name": "type", + "schema": { + "type": "string", + "enum": [ + "account", + "agent", + "inbox", + "label", + "team" + ] + }, + "required": true, + "description": "Type of report" + }, + "report_metric": { + "in": "query", + "name": "metric", + "schema": { + "type": "string", + "enum": [ + "conversations_count", + "incoming_messages_count", + "outgoing_messages_count", + "avg_first_response_time", + "avg_resolution_time", + "resolutions_count" + ] + }, + "required": true, + "description": "The type of metric" + }, + "public_inbox_identifier": { + "in": "path", + "name": "inbox_identifier", + "schema": { + "type": "string" + }, + "required": true, + "description": "The identifier obtained from API inbox channel" + }, + "public_contact_identifier": { + "in": "path", + "name": "contact_identifier", + "schema": { + "type": "string" + }, + "required": true, + "description": "The source id of contact obtained on contact create" + }, + "portal_id": { + "in": "path", + "name": "id", + "schema": { + "type": "string" + }, + "required": true, + "description": "The slug identifier of the portal" + } + }, + "securitySchemes": { + "userApiKey": { + "type": "apiKey", + "in": "header", + "name": "api_access_token", + "description": "This token can be obtained by visiting the profile page or via rails console. Provides access to endpoints based on the user permissions levels. This token can be saved by an external system when user is created via API, to perform activities on behalf of the user." + }, + "agentBotApiKey": { + "type": "apiKey", + "in": "header", + "name": "api_access_token", + "description": "This token should be provided by system admin or obtained via rails console. This token can be used to build bot integrations and can only access limited apis." + }, + "platformAppApiKey": { + "type": "apiKey", + "in": "header", + "name": "api_access_token", + "description": "This token can be obtained by the system admin after creating a platformApp. This token should be used to provision agent bots, accounts, users and their roles." + } + } + }, + "tags": [ + { + "name": "Accounts", + "description": "Account management APIs" + }, + { + "name": "Account Users", + "description": "Account user management APIs" + }, + { + "name": "AgentBots", + "description": "Bot integrations" + }, + { + "name": "Users", + "description": "User management APIs" + } + ], + "x-tagGroups": [ + { + "name": "Platform", + "tags": [ + "Accounts", + "Account Users", + "AgentBots", + "Users" + ] + }, + { + "name": "Application", + "tags": [ + "Account AgentBots", + "Agents", + "Canned Responses", + "Contacts", + "Contact Labels", + "Conversation Assignments", + "Conversation Labels", + "Conversations", + "Custom Attributes", + "Custom Filters", + "Inboxes", + "Integrations", + "Messages", + "Profile", + "Reports", + "Teams", + "Webhooks", + "Automation Rule", + "Help Center" + ] + }, + { + "name": "Client", + "tags": [ + "Contacts API", + "Conversations API", + "Messages API" + ] + }, + { + "name": "Others", + "tags": [ + "CSAT Survey Page" + ] + } + ] +} \ No newline at end of file
+ {{ t('CAPTAIN.ASSISTANTS.FORM.TEMPERATURE.DESCRIPTION') }} +
+ {{ truncatedContent }} +
Code
Hello,
This is a notification to inform you that an account has been permanently deleted from your Chatwoot instance.
+ Chatwoot Installation: {{ meta.instance_url }} + Account ID: {{ meta.account_id }} + Account Name: {{ meta.account_name }} + Deleted At: {{ meta.deleted_at }} + Marked for Deletion at: {{ meta.marked_for_deletion_at }} + Deletion Reason: {{ meta.deletion_reason }} +
+ Deleted Users ({{ meta.deleted_user_count }}): + {% for user in meta.soft_deleted_users %} + User ID: {{ user.user_id }}, Email: {{ user.user_email }}{% unless forloop.last %}{% endunless %} + {% endfor %} +
+ Deleted Users: None +
This email serves as a record for compliance purposes.
Thank you, +Chatwoot System
Caution: Any actions executed after impersonate will appear as if performed by the impersonated user - [<%= page.resource.name %> ]