Zammad System Documentation¶
Hint
You are currently reading the Zammad system documentation. There are also administrator and user manuals available.
Zammad¶
Do you receive many emails and want to answer them with a team of agents?
You’re going to love Zammad!
Zammad is a web based open source helpdesk/customer support system with many features to manage customer communication via several channels like telephone, facebook, twitter, chat and emails. It is distributed under version 3 of the GNU AFFERO General Public License (GNU AGPLv3).
The code is open source, and available on GitHub!
Software¶
1. Client requirements¶
Please note that, while Zammad being a web application, there’s some requirements for your clients. This ensures that Zammad works as expected.
1.1. Supported Browsers¶
Browser |
Remarks |
---|---|
Firefox 78+ |
(ESR) |
(Google) Chrome 83+ |
This also applies for all Chromium based Browsers like Microsoft Edge |
Opera 69+ |
(based on Chromium 83) |
Microsoft Internet Explorer 11 |
⚠️ Deprecated, will be removed with Zammad 7 |
Safari 11 |
Danger
⚠️ Deprecation warning ⚠️
Zammad 7 will no longer support Internet Explorer 11 environments. Users using IE will be forced to use a different browser.
Please note that Zammad heavily uses Javascript which makes it a hard requirement. Some browser addons that hook into page content may interfere with Zammads function which is not a bug. For example the Google Chrome translation module is known to do odd things, especially to state names. Use Zammads internal translations instead.
1.2. Network requirements¶
Zammad uses web sockets. Some application firewalls may filter these connections. This may lead to decreased browser performance.
There’s a fallback to Ajax which causes a higher application server load and thus should be avoided.
In case you’re having issues with field selection, you can activate the AJAX Mode for “Core Workflows” separately.
2. Server requirements¶
If you want to install Zammad, you need the following software.
Note
Most of the software versions listed below (unless stated as specific version) are minimum requirements of Zammad. We strongly encourage you to use most current possible versions that are not end of life.
2.1. Ruby Programming Language¶
Hint
🤓 Only relevant for source code installations
Docker and package installations provide the required ruby dependencies!
Ruby 3.1.3
Zammad |
Ruby |
---|---|
5.4+ |
3.1.3 |
5.2 - 5.3 |
3.0.4 |
5.0 - 5.1 |
2.7.4 |
3.4.1 - 4.1 |
2.6.6 |
3.4.0 |
2.6.5 |
3.1 - 3.3 |
2.5.5 |
2.5 - 3.0 |
2.4.4 |
2.2. Supported distributions¶
Zammad provides binary packages for the most recent two stable / long-term-support releases of the supported Linux distributions, until they reach their end-of-life or until they can no longer provide the technical requirements for Zammad. Using of the latest supported stable / long-term-support version is generally recommended.
Below you can find all distributions Zammad provides packages for.
Distribution |
Versions |
---|---|
CentOS / RHEL |
8 |
Debian |
11 & 12 |
OpenSuSE / SLES |
Leap 15.x / 15 |
Ubuntu |
20.04 & 22.04 |
Warning
⚠️ SuSE Tumbleweed does not meet Zammad requirements and thus is not supported!
Note
🤓 What about my specific distribution?! It’s so cool!
If you distribution is not listed, you can still install Zammad. For this you can either use Docker-Compose or Source installation.
We try to provide all current distributions that are supported by Packager.io. This means that we can’t always provide support for your favorite system.
2.3. Package Dependencies¶
The below dependencies need to be installed on your system. If you’re using the package install, the packages below will automatically installed with the Zammad-Package.
# Debian & Ubuntu
$ apt install libimlib2
# openSUSE
$ zypper install imlib2
# CentOS
$ yum install epel-release
$ yum install imlib2
Note
libimlib2-dev
or imlib2-devel
are no longer required.bundle install
for e.g. custom Gems or
development, you’ll need to install it!2.4. Database Server¶
Zammad will store all content in a Database. You can choose between the following database servers:
PostgreSQL 10+
MySQL 5.7+ / MariaDB 10.3+ (⚠️ deprecated with Zammad 7.0+)
Danger
Support for MySQL/MariaDB will be dropped in Zammad 7.0 upwards. Make sure to migrate your existing instance of Zammad to PostgreSQL.
For MySQL/MariaDB, the following configuration is required:
Use
UTF-8
encoding -utf8mb4
for example will fail!Set
max_allowed_packet
to a value larger than the default of 4 MB (64 MB+ recommended).
You may also want to consider the following settings for your MySQL server:
innodb_file_format = Barracuda
innodb_file_per_table = on
innodb_default_row_format = dynamic
innodb_large_prefix = 1
innodb_file_format_max = Barracuda
2.5. Node.js¶
Node.js is required for asset compiling.
Package installations come pre-bundled with the correct NodeJS version. A manual installation is not required unless you require NodeJS for other projects.
Node.js is only required on source code installations if you need to change
any javascript or stylesheet files via rake assets:precompile
.
Zammad |
Node.js |
---|---|
6.2+ |
18.0+ |
5.2 - 6.1 |
16.0+ |
5.0 - 5.1 |
10.0+ |
2.6. Reverse Proxy¶
In a typical web environment today, you use a reverse proxy to deliver the static content of your application. Only the “expensive” app required HTTP requests are forwarded to the application server.
The following reverse proxies are supported:
Nginx 1.3+
Apache 2.2+
Hint
Some configuration is required, please see Configure the webserver.
2.8. Elasticsearch (optional)¶
Zammad uses Elasticsearch to
make the search faster
support advanced features like reports
search for content of email attachments
This becomes increasingly important the higher the number of tickets in your system gets.
Warning
This dependency is optional but strongly recommended!
Zammad will work without it, but search performance will be degraded and the search will be very limited. We recommend using Elasticsearch, as it will boost the usage of Zammad greatly!
Hint
📦 If you install Zammad via package manager…
It’s perfectly safe to manually override the Elasticsearch dependency.
The appropriate command line flag will depend on your platform
(e.g., --force
, --ignore-depends
, --skip-broken
);
check your package manager’s manpage to find out.
Starting with Zammad 4.0 you can decide if you want to use
elasticsearch
or elasticsearch-oss
. Please note that CentOS
requires elasticsearch
.
Zammad |
Elasticsearch |
---|---|
5.2+ |
>= 7.8, < 9 |
5.0 - 5.1 |
>= 7.8, < 8 |
4.0-4.1 |
>= 6.5, <= 7.12 |
3.4-3.6 |
>= 5.5, <= 7.9 |
3.3 |
>= 2.4, <=7.6 |
3.2 |
>= 2.4, <=7.5 |
3.1 |
>= 2.4, <=7.4 |
2.0-3.0 |
>= 2.4, <=5.6 |
An Elasticsearch plugin is required to index the contents of email attachments:
ingest-attachment
.
2.9. Optional tools of improved caching and distribution¶
These features / integrations below are optional. You should consider using them to have a better performance or if you want to use the corresponding feature.
You can also have a look in our 🎛️ Performance Tuning section.
2.9.1 Memcached¶
Instead of storing Zammads cache files within your filesystem, you can also do so in Memcached. This can allow you to restrict the size of your cache directories to improve performance.
The installation and configuration is out of our scope. Please follow the official vendor guides and ensure to have a tight security on your installation.
2.10 GnuPG (optional)¶
If you want to use the PGP integration for sending and receiving signed and encrypted emails, you need to install the GnuPG-Tool. Please have a look at the official GnuPG website.
Hardware¶
You can run Zammad on bare metal or on a virtual machine. Choose what you prefer.
For Zammad and a database server like PostgreSQL we recommend at least:¶
2 CPU cores
4 GB of RAM (+4 GB if you want to run Elasticsearch on the same server)
For optimal performance up to 40 agents:¶
4 CPU cores
6 GB of RAM (+6 GB if you want to run Elasticsearch on the same server)
Of course at the end it depends on actual load of concurrent agents and data traffic.
Note
We can’t suggest any disk space recommendations, as this highly depends on how you work. Zammad will always try to recognize the same attachments and store it just once.
Performance Tuning¶
As the number of active users on your system grows, performance will eventually degrade, leading to:
delays for outgoing email,
long loading times when viewing or creating tickets,
stale or out-of-sync search results, or
stale or out-of-sync ticket overviews.
You may see modest improvements by
setting certain environment variables for Performance Tuning,
such as $WEB_CONCURRENCY
or $ZAMMAD_SESSION_JOBS_CONCURRENT
.
Install from package¶
Note
sudo
is
not used.Prerequisites¶
Additional software dependencies¶
In addition to already mentioned Package dependencies, some operating systems may require additional packages if not already installed.
$ apt install curl apt-transport-https gnupg
The openSUSE Enterprise 15 variant requires additional repositories to be activated. To do so, run the following commands.
$ SUSEConnect --product sle-module-desktop-applications/$(. /etc/os-release; echo $VERSION_ID)/$(uname -i)
$ SUSEConnect --product PackageHub/$(. /etc/os-release; echo $VERSION_ID)/$(uname -i)
$ yum install wget epel-release
Setup Elasticsearch¶
Elasticsearch is a dependency of Zammad and needs to be provided before installing Zammad. Please take a look at the following page: Set up Elasticsearch.
Ensure correct locale¶
For Zammad to function correctly, your system has to use the correct locales.
List your current locale settings.
$ locale |grep "LANG="
If above does not return <lang_code>.utf8
you can correct this issue as follows.
$ apt install locales
$ locale-gen en_US.UTF-8
$ echo "LANG=en_US.UTF-8" > /etc/default/locale
List your current locale settings.
$ locale |grep "LANG="
If above does not return <lang_code>.utf8
you can correct this issue as follows.
$ localectl set-locale LANG=en_US.utf8
List your current locale settings.
$ localectl status |grep "LC_CTYPE"
If above does not return <lang_code>.utf8
you can correct this issue as follows.
$ localectl set-locale LC_CTYPE=en_US.UTF-8
Hint
By default OpenSUSE uses POSIX
as LANG
value for the root
user. Learn more about this within the OpenSUSE documentation.
This does not affect other users and thus can be ignored.
Add Repository and install Zammad¶
Hint
Packager.io may not be accessible from IPv6-only environments, so make sure to consider this when performing the steps below.
- Add Repository
- Install Repository Key
$ curl -fsSL https://dl.packager.io/srv/zammad/zammad/key | \ gpg --dearmor | tee /etc/apt/trusted.gpg.d/pkgr-zammad.gpg> /dev/null
- Ubuntu 20.04
$ echo "deb [signed-by=/etc/apt/trusted.gpg.d/pkgr-zammad.gpg] https://dl.packager.io/srv/deb/zammad/zammad/stable/ubuntu 20.04 main"| \ tee /etc/apt/sources.list.d/zammad.list > /dev/null
- Ubuntu 22.04
$ echo "deb [signed-by=/etc/apt/trusted.gpg.d/pkgr-zammad.gpg] https://dl.packager.io/srv/deb/zammad/zammad/stable/ubuntu 22.04 main"| \ tee /etc/apt/sources.list.d/zammad.list > /dev/null
- Install Repository Key
$ curl -fsSL https://dl.packager.io/srv/zammad/zammad/key | \ gpg --dearmor | tee /etc/apt/trusted.gpg.d/pkgr-zammad.gpg> /dev/null
- Debian 11
$ echo "deb [signed-by=/etc/apt/trusted.gpg.d/pkgr-zammad.gpg] https://dl.packager.io/srv/deb/zammad/zammad/stable/debian 11 main"| \ tee /etc/apt/sources.list.d/zammad.list > /dev/null
- Debian 12
$ echo "deb [signed-by=/etc/apt/trusted.gpg.d/pkgr-zammad.gpg] https://dl.packager.io/srv/deb/zammad/zammad/stable/debian 12 main"| \ tee /etc/apt/sources.list.d/zammad.list > /dev/null
- Install Repository Key
$ rpm --import https://dl.packager.io/srv/zammad/zammad/key
- RHEL 8 / CentOS 8
$ wget -O /etc/yum.repos.d/zammad.repo \ https://dl.packager.io/srv/zammad/zammad/stable/installer/el/8.repo
- Install Repository Key
$ rpm --import https://dl.packager.io/srv/zammad/zammad/key
- SLES 15 / openSUSE 15.x
$ wget -O /etc/zypp/repos.d/zammad.repo \ https://dl.packager.io/srv/zammad/zammad/stable/installer/sles/15.repo
- Install Zammad
$ apt update $ apt install zammad
# general $ yum install zammad
Due to an issue with packager.io on CentOS 8 you’ll need to correct file permissions for public files.
chmod -R 755 /opt/zammad/public/
$ zypper ref $ zypper install zammad
Firewall & SELinux¶
Some parts of these steps may not apply to you, feel free to skip them!
SELinux¶
$ # Allow nginx or apache to access public files of Zammad and communicate
$ chcon -Rv --type=httpd_sys_content_t /opt/zammad/public/
$ setsebool httpd_can_network_connect on -P
$ semanage fcontext -a -t httpd_sys_content_t /opt/zammad/public/
$ restorecon -Rv /opt/zammad/public/
$ chmod -R a+r /opt/zammad/public/
See the documentation for more input if you wish to continue.
Firewall¶
Note
Below only covers the distribution’s default firewall. It may not cover your case.
$ # Open Port 80 and 443 on your Firewall
$ ufw allow 80
$ ufw allow 443
$ ufw reload
Warning
We’re covering nftables
in this part - iptables is discouraged
starting from Debian 10 (Buster).
Our example uses the input
chain, yours may be a different one!
Add the following lines to /etc/nftables.conf
or your specific rule
file. Ensure to add these lines to your input-chain.
# Open Port 80 and 443 for Zammad
tcp dport { http, https } accept
udp dport { http, https } accept
The result should look like the following. Keep in mind that your enviroment could require different / more rules.
#!/usr/local/sbin/nft -f
flush ruleset
table inet filter {
chain input {
type filter hook input priority 0; policy drop;
ct state established,related accept
tcp dport ssh log accept
tcp dport { http, https } accept
udp dport { http, https } accept
}
chain forward {
type filter hook forward priority 0; policy accept;
}
chain output {
type filter hook output priority 0; policy accept;
}
}
To load your new rules, simply run systemctl reload nftables
.
$ # Open Port 80 and 443 on your Firewall
$ firewall-cmd --zone=public --add-service=http --permanent
$ firewall-cmd --zone=public --add-service=https --permanent
$ firewall-cmd --reload
If your system does not yet know webserver rules, you can add a new one
for your firewall by creating the file
/etc/sysconfig/SuSEfirewall2.d/services/webserver
with this content:
## Name: Webserver
## Description: Open ports for HTTP and HTTPs
# space separated list of allowed TCP ports
TCP="http https"
# space separated list of allowed UDP ports
UDP="http https"
After that locate FW_CONFIGURATIONS_EXT
within
/etc/sysconfig/SuSEfirewall2
and add the option webserver
to the
list. The list is seperated by spaces.
You may require a different zone, above covers the external zone.
Now ensure to restart the firewall service.
systemctl restart SuSEfirewall2
If we didn’t cover your distribution or firewall in question, ensure to
open ports 80
and 443
(TCP & UDP) beside of the ports you need.
Manage services of Zammad¶
In general Zammad uses three services - these can be (re)started & stopped
with the parent zammad
.
$ # Zammad service to start all services at once
$ systemctl (status|start|stop|restart) zammad
$ # Zammads internal puma server (relevant for displaying the web app)
$ systemctl (status|start|stop|restart) zammad-web
$ # Zammads background worker - relevant for all delayed- and background jobs
$ systemctl (status|start|stop|restart) zammad-worker
$ # Zammads websocket server for session related information
$ systemctl (status|start|stop|restart) zammad-websocket
Next steps¶
With this Zammad technically is ready to go. However, you’ll need to follow the following further steps to access Zammads Web-UI and getting started with it.
You may also find Zammads Console commands useful
If you expect usage with 5 agents or more you may also want to consider the following pages.
Install from source¶
The source installation is the most difficult installation type of Zammad. If you’re not too experienced with Linux and all that, you may want to use another installation type:
Note
Please note that we only use sudo
after direct user changes.
In all other situations you can expect root
being in charge.
Hint
🔎 Looking for MacOS hints? You can find the developer documentation here.
Prerequisites¶
Software dependencies¶
Please ensure that you already provided mentioned Software requirements.
Add user¶
$ useradd zammad -m -d /opt/zammad -s /bin/bash
$ groupadd zammad
Installation¶
Step 1: Get the source¶
Note
Not all distributions ship wget
and tar
by default, you may need to
install it manually.
Get the latest stable release of Zammad here. This file will be updated whenever new bug-fixes are applied, so you can update from this URL regularly.
$ cd /opt
$ wget https://ftp.zammad.com/zammad-latest.tar.gz
$ tar -xzf zammad-latest.tar.gz --strip-components 1 -C zammad
$ chown -R zammad:zammad zammad/
$ rm -f zammad-latest.tar.gz
Step 2: Install dependencies¶
Note
Please have a look at Configure the webserver for detailed instructions.
Zammad requires specific ruby versions. Adapt the commands below if you install older versions. A list of required versions can be found on the Software requirements page.
$ apt update
$ apt install postgresql postgresql-contrib
$ systemctl start postgresql
$ systemctl enable postgresql
# CentOS 8
$ yum install postgresql-server postgresql-contrib
$ postgresql-setup initdb
$ systemctl start postgresql
$ systemctl enable postgresql
$ zypper refresh
$ zypper install postgresql postgresql-server postgresql-contrib
# openSuSE 15 also requires:
$ zypper install postgresql-server-devel
$ systemctl start postgresql
$ systemctl enable postgresql
- Install Node.js
# see https://github.com/nodesource/distributions#debian-and-ubuntu-based-distributions # for detailed installation instructions. $ apt install -y ca-certificates curl gnupg $ mkdir -p /etc/apt/keyrings $ curl -fsSL https://deb.nodesource.com/gpgkey/nodesource-repo.gpg.key | sudo gpg --dearmor -o /etc/apt/keyrings/nodesource.gpg $ NODE_MAJOR=20 echo "deb [signed-by=/etc/apt/keyrings/nodesource.gpg] https://deb.nodesource.com/node_$NODE_MAJOR.x nodistro main" | sudo tee /etc/apt/sources.list.d/nodesource.list $ apt update $ apt install nodejs -y
- Install RVM
$ apt install curl git patch build-essential bison zlib1g-dev libssl-dev libxml2-dev libxml2-dev autotools-dev\ libxslt1-dev libyaml-0-2 autoconf automake libreadline-dev libyaml-dev libtool libgmp-dev libgdbm-dev libncurses5-dev\ pkg-config libffi-dev libimlib2-dev gawk software-properties-common $ apt-add-repository -y ppa:rael-gc/rvm $ apt update $ apt install rvm
- Set relevant Environment variables
# Set rails environment specific things $ echo "export RAILS_ENV=production" >> /opt/zammad/.bashrc $ echo "export RAILS_SERVE_STATIC_FILES=true" >> /opt/zammad/.bashrc $ echo "rvm --default use 3.1.3" >> /opt/zammad/.bashrc # Debian, CentOS & OpenSuSE $ echo "source /usr/local/rvm/scripts/rvm" >> /opt/zammad/.bashrc # Ubuntu $ echo "source /usr/share/rvm/scripts/rvm" >> /opt/zammad/.bashrc
- Install Ruby Environment
# Add zammad user to RVM group $ usermod -a -G rvm zammad # Install Ruby 3.1.3 $ su - zammad $ rvm install ruby-3.1.3 # Install bundler, rake and rails $ rvm use ruby-3.1.3 $ gem install bundler rake rails
- Install Node.js
# see https://github.com/nodesource/distributions#debian-and-ubuntu-based-distributions # for detailed installation instructions. $ apt install -y ca-certificates curl gnupg $ mkdir -p /etc/apt/keyrings $ curl -fsSL https://deb.nodesource.com/gpgkey/nodesource-repo.gpg.key | sudo gpg --dearmor -o /etc/apt/keyrings/nodesource.gpg $ NODE_MAJOR=20 echo "deb [signed-by=/etc/apt/keyrings/nodesource.gpg] https://deb.nodesource.com/node_$NODE_MAJOR.x nodistro main" | sudo tee /etc/apt/sources.list.d/nodesource.list $ apt update $ apt install nodejs -y
- Install RVM
$ apt install curl git patch build-essential bison zlib1g-dev libssl-dev libxml2-dev libxml2-dev autotools-dev\ libxslt1-dev libyaml-0-2 autoconf automake libreadline-dev libyaml-dev libtool libgmp-dev libgdbm-dev libncurses5-dev\ pkg-config libffi-dev libimlib2-dev gawk $ gpg --keyserver keyserver.ubuntu.com --recv-keys\ 409B6B1796C275462A1703113804BB82D39DC0E3 7D2BAF1CF37B13E2069D6956105BD0E739499BDB $ curl -L https://get.rvm.io | bash -s stable
- Set relevant Environment variables
# Set rails environment specific things $ echo "export RAILS_ENV=production" >> /opt/zammad/.bashrc $ echo "export RAILS_SERVE_STATIC_FILES=true" >> /opt/zammad/.bashrc $ echo "rvm --default use 3.1.3" >> /opt/zammad/.bashrc # Debian, CentOS & OpenSuSE $ echo "source /usr/local/rvm/scripts/rvm" >> /opt/zammad/.bashrc # Ubuntu $ echo "source /usr/share/rvm/scripts/rvm" >> /opt/zammad/.bashrc
- Install Ruby Environment
# Add zammad user to RVM group $ usermod -a -G rvm zammad # Install Ruby 3.1.3 $ su - zammad $ rvm install ruby-3.1.3 # Install bundler, rake and rails $ rvm use ruby-3.1.3 $ gem install bundler rake rails
- Install Node.js
$ curl -fsSL https://rpm.nodesource.com/setup_lts.x | bash -
- Install RVM
$ yum install epel-release $ yum install patch autoconf automake bison bzip2 gcc-c++ libffi-devel libtool make patch readline-devel ruby\ zlib-devel glibc-headers glibc-devel openssl-devel git imlib2 imlib2-devel $ gpg --keyserver keyserver.ubuntu.com --recv-keys\ 409B6B1796C275462A1703113804BB82D39DC0E3 7D2BAF1CF37B13E2069D6956105BD0E739499BDB $ curl -L https://get.rvm.io | bash -s stable
- Set relevant Environment variables
# Set rails environment specific things $ echo "export RAILS_ENV=production" >> /opt/zammad/.bashrc $ echo "export RAILS_SERVE_STATIC_FILES=true" >> /opt/zammad/.bashrc $ echo "rvm --default use 3.1.3" >> /opt/zammad/.bashrc # Debian, CentOS & OpenSuSE $ echo "source /usr/local/rvm/scripts/rvm" >> /opt/zammad/.bashrc # Ubuntu $ echo "source /usr/share/rvm/scripts/rvm" >> /opt/zammad/.bashrc
- Install Ruby Environment
# Add zammad user to RVM group $ usermod -a -G rvm zammad # Install Ruby 3.1.3 $ su - zammad $ rvm install ruby-3.1.3 # Install bundler, rake and rails $ rvm use ruby-3.1.3 $ gem install bundler rake rails
- Install Node.js
$ zypper install nodejs16
- Install RVM
$ zypper install patch autoconf automake bison bzip2 gcc-c++ libffi-devel libtool make patch readline-devel\ zlib-devel glibc-devel openssl-devel git imlib2 imlib2-devel gdbm-devel libyaml-devel $ gpg --keyserver keyserver.ubuntu.com --recv-keys\ 409B6B1796C275462A1703113804BB82D39DC0E3 7D2BAF1CF37B13E2069D6956105BD0E739499BDB $ curl -L https://get.rvm.io | bash -s stable
- Set relevant Environment variables
# Set rails environment specific things $ echo "export RAILS_ENV=production" >> /opt/zammad/.bashrc $ echo "export RAILS_SERVE_STATIC_FILES=true" >> /opt/zammad/.bashrc $ echo "rvm --default use 3.1.3" >> /opt/zammad/.bashrc # Debian, CentOS & OpenSuSE $ echo "source /usr/local/rvm/scripts/rvm" >> /opt/zammad/.bashrc # Ubuntu $ echo "source /usr/share/rvm/scripts/rvm" >> /opt/zammad/.bashrc
- Install Ruby Environment
# Add zammad user to RVM group $ usermod -a -G rvm zammad # Install Ruby 3.1.3 $ su - zammad $ rvm install ruby-3.1.3 # Install bundler, rake and rails $ rvm use ruby-3.1.3 $ gem install bundler rake rails
Other systems than above mentioned are out of scope of this documentation. Please check the rvm documentation on how to install rvm on your system.
Please also ensure to install nodejs
.
After that install the specific required ruby version.
- Install PostgreSQL Dependencies
$ apt install libpq-dev
# CentOS 8 $ yum install postgresql-libs postgresql-devel
$ zypper install postgresql-devel
- Install Gems for Zammad
$ su - zammad $ bundle config set without "test development mysql" $ bundle install
Step 3: Configure database settings¶
Tip
🤓 For easiest usage …
If you provide your Zammad user with database creation permission, you can
run db:create
in the following section. If you don’t want that, you’ll
have to create the database manually.
$ cp config/database/database.yml config/database.yml
$ vi config/database.yml
Here’s a sample configuration to give you an idea on how your configuration file could look like. Please also have a look at Configure Database server for deeper details.
production: adapter: postgresql database: zammad pool: 50 encoding: utf8 username: zammad password: changeme # You can remove this line if you enable socket based authentication!
Hint
If you want to use an existing database server that’s not on the same
machine, you can also use host
and port
to set that up.
For security reasons, ensure that your database configuration is readable for the Zammad user only.
$ chmod 600 /opt/zammad/config/database.yml
$ chown zammad:zammad /opt/zammad/config/database.yml
Step 4: Initialize your database¶
Warning
Ensure to do this as zammad
user in your Zammad directory!
Tip
🤓 Avoid a restart …
You can set the base URL of your Zammad installation by setting the ZAMMAD_HTTP_TYPE and ZAMMAD_FQDN environment variables before initializing the database (see below).
# Example for a base URL of https://zammad.example.com
$ su - zammad
$ export ZAMMAD_HTTP_TYPE=https
$ export ZAMMAD_FQDN=zammad.example.com
$ su - zammad
$ rake db:create # SKIP IF you already created zammads database (see tip of step 3)
$ rake db:migrate
$ rake db:seed
# Synchronize translations
$ rails r "Locale.sync"
$ rails r "Translation.sync"
Step 5: Pre compile all Zammad assets¶
$ rake assets:precompile
Step 6: Start Zammad or install as service¶
Note
Run the following commands as root
.
You can start all services by hand or use systemd to start / stop Zammad.
$ cd /opt/zammad/script/systemd
$ ./install-zammad-systemd-services.sh
Warning
This method is not suitable for production use - you should avoid it.
$ rails s -p 3000 # application web server
$ script/websocket-server.rb start # non blocking websocket server
$ script/background-worker.rb start # generate overviews on demand, just send changed data to browser
Danger
⚠️ Zammads background worker cannot run in daemon mode!
Manage services of Zammad¶
In general Zammad uses three services - these can be (re)started & stopped
with the parent zammad
.
$ # Zammad service to start all services at once
$ systemctl (status|start|stop|restart) zammad
$ # Zammads internal puma server (relevant for displaying the web app)
$ systemctl (status|start|stop|restart) zammad-web
$ # Zammads background worker - relevant for all delayed- and background jobs
$ systemctl (status|start|stop|restart) zammad-worker
$ # Zammads websocket server for session related information
$ systemctl (status|start|stop|restart) zammad-websocket
Firewall & SELinux¶
Some parts of these steps may not apply to you, feel free to skip them!
SELinux¶
$ # Allow nginx or apache to access public files of Zammad and communicate
$ chcon -Rv --type=httpd_sys_content_t /opt/zammad/public/
$ setsebool httpd_can_network_connect on -P
$ semanage fcontext -a -t httpd_sys_content_t /opt/zammad/public/
$ restorecon -Rv /opt/zammad/public/
$ chmod -R a+r /opt/zammad/public/
See the documentation for more input if you wish to continue.
Firewall¶
Note
Below only covers the distribution’s default firewall. It may not cover your case.
$ # Open Port 80 and 443 on your Firewall
$ ufw allow 80
$ ufw allow 443
$ ufw reload
Warning
We’re covering nftables
in this part - iptables is discouraged
starting from Debian 10 (Buster).
Our example uses the input
chain, yours may be a different one!
Add the following lines to /etc/nftables.conf
or your specific rule
file. Ensure to add these lines to your input-chain.
# Open Port 80 and 443 for Zammad
tcp dport { http, https } accept
udp dport { http, https } accept
The result should look like the following. Keep in mind that your enviroment could require different / more rules.
#!/usr/local/sbin/nft -f
flush ruleset
table inet filter {
chain input {
type filter hook input priority 0; policy drop;
ct state established,related accept
tcp dport ssh log accept
tcp dport { http, https } accept
udp dport { http, https } accept
}
chain forward {
type filter hook forward priority 0; policy accept;
}
chain output {
type filter hook output priority 0; policy accept;
}
}
To load your new rules, simply run systemctl reload nftables
.
$ # Open Port 80 and 443 on your Firewall
$ firewall-cmd --zone=public --add-service=http --permanent
$ firewall-cmd --zone=public --add-service=https --permanent
$ firewall-cmd --reload
If your system does not yet know webserver rules, you can add a new one
for your firewall by creating the file
/etc/sysconfig/SuSEfirewall2.d/services/webserver
with this content:
## Name: Webserver
## Description: Open ports for HTTP and HTTPs
# space separated list of allowed TCP ports
TCP="http https"
# space separated list of allowed UDP ports
UDP="http https"
After that locate FW_CONFIGURATIONS_EXT
within
/etc/sysconfig/SuSEfirewall2
and add the option webserver
to the
list. The list is seperated by spaces.
You may require a different zone, above covers the external zone.
Now ensure to restart the firewall service.
systemctl restart SuSEfirewall2
If we didn’t cover your distribution or firewall in question, ensure to
open ports 80
and 443
(TCP & UDP) beside of the ports you need.
Next steps¶
With this Zammad technically is ready to go. However, you’ll need to follow the following further steps to access Zammads Web-UI and getting started with it.
You may also find Zammads Console commands useful
If you expect usage with 5 agents or more you may also want to consider the following pages.
Set up Elasticsearch¶
Zammad’s search function is powered by Elasticsearch, and requires the ingest attachment plugin.
This guide uses the zammad run
command prefix in command line examples.
This prefix is only applicable to package installations
(i.e., via apt/yum/zypper, or .deb
/.rpm
files).
If you installed from source, be sure to omit this prefix
and run the bare rails ...
or rake ...
commands instead.
Step 1: Installation¶
Starting with Zammad 4.0, our packages allow you to decide whether to use
elasticsearch
or elasticsearch-oss
.
elasticsearch-oss
users please use below “direct download” tab for
further installation steps.
Warning
Above does not apply to CentOS because of compatibility reasons.
$ apt install apt-transport-https sudo wget curl gnupg
$ echo "deb [signed-by=/etc/apt/trusted.gpg.d/elasticsearch.gpg] https://artifacts.elastic.co/packages/7.x/apt stable main"| \
tee -a /etc/apt/sources.list.d/elastic-7.x.list > /dev/null
$ curl -fsSL https://artifacts.elastic.co/GPG-KEY-elasticsearch | \
gpg --dearmor | tee /etc/apt/trusted.gpg.d/elasticsearch.gpg> /dev/null
$ apt update
$ apt install elasticsearch
$ /usr/share/elasticsearch/bin/elasticsearch-plugin install ingest-attachment
$ apt install apt-transport-https sudo wget curl gnupg
$ echo "deb [signed-by=/etc/apt/trusted.gpg.d/elasticsearch.gpg] https://artifacts.elastic.co/packages/7.x/apt stable main"| \
tee -a /etc/apt/sources.list.d/elastic-7.x.list > /dev/null
$ curl -fsSL https://artifacts.elastic.co/GPG-KEY-elasticsearch | \
gpg --dearmor | tee /etc/apt/trusted.gpg.d/elasticsearch.gpg> /dev/null
$ apt update
$ apt install elasticsearch
$ /usr/share/elasticsearch/bin/elasticsearch-plugin install ingest-attachment
$ rpm --import https://artifacts.elastic.co/GPG-KEY-elasticsearch
$ echo "[elasticsearch-7.x]
name=Elasticsearch repository for 7.x packages
baseurl=https://artifacts.elastic.co/packages/7.x/yum
gpgcheck=1
gpgkey=https://artifacts.elastic.co/GPG-KEY-elasticsearch
enabled=1
autorefresh=1
type=rpm-md"| tee /etc/yum.repos.d/elasticsearch-7.x.repo
$ yum install -y elasticsearch
$ /usr/share/elasticsearch/bin/elasticsearch-plugin install ingest-attachment
$ rpm --import https://artifacts.elastic.co/GPG-KEY-elasticsearch
$ echo "[elasticsearch-7.x]
name=Elasticsearch repository for 7.x packages
baseurl=https://artifacts.elastic.co/packages/7.x/yum
gpgcheck=1
gpgkey=https://artifacts.elastic.co/GPG-KEY-elasticsearch
enabled=1
autorefresh=1
type=rpm-md"| tee /etc/zypp/repos.d/elasticsearch-7.x.repo
$ zypper install elasticsearch
$ /usr/share/elasticsearch/bin/elasticsearch-plugin install ingest-attachment
Find the latest release on the downloads page, or see the installation guide for in-depth instructions. Ensure to also install the fitting (and mandatory!) attachment plugin for elasticsearch.
If you prefer the Open Source version of Elasticsearch, please use the Elasticsearch-OSS download page.
# Install the attachment plugin
$ /usr/share/elasticsearch/bin/elasticsearch-plugin install ingest-attachment
# Increase the virtual memory map limit
$ sysctl -w vm.max_map_count=262144
After you installed Elasticsearch and its attachment plugin, ensure to enable it by default and start it.
$ systemctl start elasticsearch
$ systemctl enable elasticsearch
Note
🐋 Docker installations on macOS/Windows:
Setting the vm.max_map_count
kernel parameter requires
additional steps.
Step 2: Suggested Configuration¶
We use the following settings to optimize the performance of our Elasticsearch servers. Your mileage may vary.
# /etc/elasticsearch/elasticsearch.yml
# Tickets above this size (articles + attachments + metadata)
# may fail to be properly indexed (Default: 100mb).
#
# When Zammad sends tickets to Elasticsearch for indexing,
# it bundles together all the data on each individual ticket
# and issues a single HTTP request for it.
# Payloads exceeding this threshold will be truncated.
#
# Performance may suffer if it is set too high.
http.max_content_length: 400mb
# Allows the engine to generate larger (more complex) search queries.
# Elasticsearch will raise an error or deprecation notice if this value is too low,
# but setting it too high can overload system resources (Default: 1024).
#
# Available in version 6.6+ only.
indices.query.bool.max_clause_count: 2000
Note
For more information on the indices.query.bool.max_clause_count
setting,
see the Elasticsearch 6.6 release notes.
Step 3: Connect Zammad¶
Before proceeding here, make sure to install Zammad before running below commands, as this will fail otherwise.
# Set the Elasticsearch server address
$ zammad run rails r "Setting.set('es_url', 'http://localhost:9200')"
# Build the search index
$ zammad run rake zammad:searchindex:rebuild
# Optionally, you can specify a number of CPU cores which are used for
# rebuilding the searchindex, as in the following example with 8 cores:
$ zammad run rake zammad:searchindex:rebuild[8]
Starting with Elasticsearch 8+, you need to use a HTTPS URL in ‘es_url’ as ‘https://localhost:9200’ and configure an authentication (see HTTP Basic below).
Optional settings¶
# HTTP Basic
$ zammad run rails r "Setting.set('es_user', '<username>')"
$ zammad run rails r "Setting.set('es_password', '<password>')"
Hint
🤔 How do I set up authentication on my Elasticsearch server?
Elasticsearch provides many different authentication methods. Some of them may require paid X-Pack, please check the elastic documentation for more information.
Useful when connecting multiple services or Zammad instances to a single Elasticsearch server (to prevent name collisions during indexing).
$ zammad run rails r "Setting.set('es_index', Socket.gethostname.downcase + '_zammad')"
Zammad supports searching by the contents of file attachments, which means Elasticsearch has to index those, too.
Limiting such indexing can help conserve system resources.
# Files with these extensions will not be indexed
$ zammad run rails r "Setting.set('es_attachment_ignore',\
[ '.png', '.jpg', '.jpeg', '.mpeg', '.mpg', '.mov', '.bin', '.exe', '.box', '.mbox' ] )"
# Files larger than this size (in MB) will not be indexed
$ zammad run rails r "Setting.set('es_attachment_max_size_in_mb', 50)"
Change your Elasticsearch URL if you have a separate Elasticsearch server.
Default is localhost
.
$ zammad run rails r "Setting.set('es_url', 'https://example.com')"
You can define if a SSL verification will be performed. Default is
true
.
# Deactivating SSL verification is NOT recommended
$ zammad run rails r "Setting.set('es_ssl_verify', false)"
Hint
🤔 But how to handle an Elasticsearch server with custom certificates?
You can import custom certificates and custom CA certificates to Zammad. Please have a look in the admin documentation.
Appendix¶
List of Indexed Attributes¶
Below is a comprehensive list of all object attributes indexed by Elasticsearch. In other words, if you wish to find a ticket, article, or user via the Zammad search box, Elasticsearch can match on any (or all) of the fields below.
Table of content
Note
These fields may vary if you created custom fields (objects) in the admin interface.
Hint
Below you can find some hints:
(SLA): Attributes marked as SLA attribute are only set if the ticket is affected by SLA calculation. Please note that some attributes may not be set if specific conditions are not met.
Also note that some attributes may be reset to
null
if no longer applicable.note
attribute: Note attributes usually are empty if not specified via console or API.Timestamps: All timestamps provided by Zammad are UTC by default. This also applies to times provided by Elasticsearch
Ticket¶
Tip
🤓 The following indice contains below mentioned information:
*_ticket
Field |
Sample Value |
Description |
---|---|---|
article |
#{Article Array} |
Array with all articles belonging to the ticket
Please see Article for more details
|
article_count |
|
Number of articles within the ticket |
close_at |
|
First close time, set once |
close_diff_in_min |
|
Depends on |
close_escalation_at |
|
Time stamp when the ticket would escalate in case solution time is violated. (SLA) |
close_in_min |
|
Value in minutes for how long the ticket was open based on business hours. (SLA) |
create_article_sender |
|
Sender of the article (System, Agent, Customer) |
create_article_sender_id |
|
ID of the user that created the article |
create_article_type |
|
Information of first article type and nature |
create_article_type_id |
|
Type ID of first article |
created_at |
|
Time stamp of ticket creation |
created_by |
#{user object} |
Complete Payload of user that created the ticket
Please see User for more
|
created_by_id |
|
User ID that created the ticket |
customer |
#{user object} |
Complete payload of the customer that created the ticket
Please see User for more
|
customer_id |
|
Customers User ID |
escalation_at |
|
Time stamp of the next applicable escalation. One of the following attributes:
(SLA) |
first_response_at |
|
Time stamp of the first communication type reaction to the customer (SLA) |
first_response_diff_in_min |
|
Depends on |
first_response_in_min |
|
Value in minutes for how long the first response took based on the business hours. (SLA) |
group |
#{group object} |
Complete payload of the current tickets group
Please see Group for more
|
group_id |
|
ID of the current group |
id |
|
ID of the Ticket |
last_contact_agent_at |
|
Time stamp of last communication type contact of any agent |
last_contact_at |
|
Time stamp of last communication type contact
Depends on
last_contact_agent_at , last_contact_customer_at
and “Ticket Last Contact Behaviour” setting |
last_contact_customer_at |
|
Time stamp of last communication type contact of customer |
mention_user_ids |
|
Array with mentioned or subscribed users IDs |
note |
|
Note of ticket, only set via console or API |
number |
|
Ticket number |
organization |
|
Complete Payload of user that owns the ticket
Please see Organization for more
|
organization_id |
|
ID of the customers organization |
owner |
|
Complete Payload of user that owns the ticket
Please see User for more
|
owner_id |
|
User ID of the ticket owner |
pending_time |
|
Depends on pending states, time stamp for pending time |
preferences |
|
May not be available in your system, contains information for internal system functions |
priority |
#{priority object} |
Complete Payload of priority of ticket
Please see Ticket Priority for more
|
priority_id |
|
Priority ID of the ticket |
state |
#{state object} |
Complete Payload of current ticket state
Please see Ticket State for more
|
state_id |
|
ID of current ticket state |
tags |
|
Array with all attached tags |
time_unit |
|
Accounted time units for ticket (total) |
title |
|
Title / Subject of Ticket |
type |
|
Ticket type (deprecated) |
update_diff_in_min |
|
Depends on |
update_escalation_at |
|
Time stamp when the ticket would escalate in case update time is violated. (SLA) |
update_in_min |
|
Value in minutes for how long the last ticket update took based on the business hours and update time. (SLA) |
updated_at |
|
Last ticket update |
updated_by |
#{user object} |
Complete Payload of the user that updated the ticket
Please see User for more
|
updated_by_id |
|
User ID that updated the ticket |
Ticket Priority¶
Tip
🤓 The following indice contains below mentioned information:
*_ticket_priority
Field |
Sample Value |
Description |
---|---|---|
active |
|
Defines if the priority is active (available) |
created_at |
|
Creation date of priority |
created_by_id |
|
User that created priority |
default_create |
|
Defines if priority is default priority upon ticket creation |
id |
|
ID of priority |
name |
|
Priority name |
note |
|
Note for priority that has been set via console or API |
ui_color |
|
CSS class for tickets of priority |
ui_icon |
|
CSS class for ticket icons of priority |
updated_at |
|
Date of last change |
updated_by_id |
|
User ID of user last updating the priority |
Ticket State¶
Tip
🤓 The following indice contains below mentioned information:
*_ticket_state
Field |
Sample Value |
Description |
---|---|---|
active |
|
Defines if state is active (available) |
created_at |
|
Creation date |
created_by_id |
|
User ID that created state |
default_create |
|
Defines if the state is the default state upon ticket creation |
default_follow_up |
|
Defines if the state is the default follow up state on ticket follow ups |
id |
|
State ID |
ignore_escalation |
|
Defines if SLA calculation is generally ignored for this state |
name |
|
State name |
next_state |
|
Contains all follow up state information if applicable, may not be available depending on the state type |
next_state_id |
|
State ID of follow up state |
note |
|
Note that has been set via console or API |
state_type |
|
Contains all available information of the states type |
state_type_id |
|
ID of the state type |
updated_at |
|
Last update of state |
updated_by_id |
|
User ID that updated state last |
Article¶
Tip
🤓 The following indice contains below mentioned information:
*_ticket
Note
Articles are part of the ticket index. To reduce complexity we decided to provide it in its own table. 🙏
Field |
Sample Value |
Description |
---|---|---|
body |
|
Article body in plain text |
cc |
|
EMail-Addresses set as CC (String) |
content_type |
|
Content type of article |
created_at |
|
Time stamp of article creation |
created_by_id |
|
User ID that created the article |
from |
|
From field of article creator |
id |
|
Internal article ID |
in_reply_to |
|
In-Reply-To Header from emails if applicable |
internal |
|
Defines if article is internal |
message_id |
|
Message ID of Email if applicable |
origin_by_id |
|
User ID or original creator if created on behalf another user |
preferences |
|
Internal preferences, may be empty, mainly for delivery states |
references |
|
Contains message references |
reply_to |
|
Contains reply to header if applicable |
sender_id |
|
ID of sender type (Customer, System, Agent) |
subject |
|
Article subject |
ticket_id |
|
Ticket ID the article belongs to |
to |
|
EMail address from TO-Header |
type_id |
|
ID of articles Type (phone, email, web, …) |
updated_at |
|
Last update |
updated_by_id |
|
User that updated article |
User¶
Tip
🤓 The following indice contains below mentioned information:
*_user
Field |
Sample Value |
Description |
---|---|---|
active |
|
Defines if user is active |
address |
|
Address string |
city |
|
City string |
country |
|
Country string |
created_at |
|
Creation date of user |
created_by_id |
|
User ID that created the user |
department |
|
Department string |
|
EMail Address of user, if applicable |
|
fax |
|
Fax number |
firstname |
|
Users first name |
id |
|
Internal User ID |
last_login |
|
Updated upon every user login |
lastname |
|
Users last name |
login |
|
Login name, always set and unique, can differ from |
mobile |
|
Mobile phone number |
note |
|
Note being available via web, console and API |
organization |
#{organization object} |
Complete Payload of the organization the user is member of
Please see Organization for more
|
organization_id |
|
ID of organization the user is member of |
out_of_office |
|
Defines if user has activated out of office function |
out_of_office_end_at |
|
Ending date out of office |
out_of_office_replacement_id |
|
User ID that replaces this user during out of office period |
out_of_office_start_at |
|
Begin date out of office |
permissions |
|
Array with all permissions of the user |
phone |
|
Phone number of user |
preferences |
|
Depends on user and situation, may contain |
role_ids |
|
Contains array with role IDs assigned to the user |
street |
|
Street |
updated_at |
|
Time stamp of last update |
updated_by_id |
|
User ID that updated this entry |
verified |
|
Defines if the user has verified the account |
vip |
|
Defines if user has VIP state |
web |
|
Web URL of User |
zip |
|
ZIP code |
Organization¶
Tip
🤓 The following indice contains below mentioned information:
*_organization
Field |
Sample Value |
Description |
---|---|---|
active |
|
Defines if organization is active |
created_at |
2021-03-22T12:47:54.807Z |
Creation date |
created_by |
#{user object} |
Complete Payload of the user that created the organzation
Please see User for more
|
created_by_id |
|
User ID that created the organization |
domain |
|
Organizations domain |
domain_assignment |
|
Domain assignment depends on |
id |
|
Organization ID |
members |
#{array of user objects} |
Array with complete Payload of the users being member of the
organization
Please see User for more
|
name |
|
Organization name |
note |
|
Note being available via web, console and API |
shared |
|
Defines if the organization is a sharing one |
updated_at |
|
Last update time |
updated_by |
#{user object} |
Complete Payload of the user that updated the organization
Please see User for more
|
updated_by_id |
|
User ID that updated the organization |
vip |
|
Defines if the organization has VIP state |
Group¶
Tip
🤓 The following indice contains below mentioned information:
*_group
Field |
Sample Value |
Description |
---|---|---|
active |
|
Defines if group is active (available) |
assignment_timeout |
|
Time in minutes an agent can be inactive until the owner ship is removed |
created_at |
|
Time stamp of group creation |
created_by_id |
|
User ID that created the group |
email_address |
|
Contains all available information of the groups email address |
email_address_id |
|
ID of email address |
follow_up_assignment |
|
Defines if owners are still assigned after follow ups |
follow_up_possible |
|
Defines if following up on a closed ticket is possible |
id |
|
Group ID |
name |
|
Group name |
note |
|
Notes for the group available via web, console and API |
signature |
|
Contains all available information of the groups signature |
signature_id |
|
Signature ID |
updated_at |
|
Time stamp of last group update |
updated_by_id |
|
User ID that updated group |
CTI Log¶
Tip
🤓 The following indice contains below mentioned information:
*_cti_log
Field |
Sample Value |
Description |
---|---|---|
call_id |
|
Unique Call ID |
comment |
|
Optional comment |
created_at |
|
Creation date of Call |
direction |
|
Call direction |
done |
|
Defines if call displays as “to do” within UI |
duration_talking_time |
|
Call duration in seconds |
duration_waiting_time |
|
Duration in seconds the caller was waiting for answer |
end_at |
|
Time stamp of call end |
from |
|
Calling number |
from_comment |
|
Display name of calling number if applicable |
from_pretty |
|
Pretty version of |
id |
|
Internal ID of entry |
initialized_at |
2021-03-25T08:47:56.753Z |
Time stamp of call initialization, usually matches |
preferences |
|
Contains internal information if required |
queue |
|
Queue the call was answered in |
start_at |
|
Time stamp the call was answered |
state |
|
Last state of call |
to |
|
Dialed number |
to_comment |
|
Display name of called number if applicable |
to_pretty |
|
Pretty version of |
updated_at |
|
Last update of entry |
Chat Session¶
Tip
🤓 The following indice contains below mentioned information:
*_chat_session
Field |
Sample Value |
Description |
---|---|---|
chat |
|
Contains various preferences of the chat topic in charge |
chat_id |
|
ID of Chat topic |
created_at |
|
Time stamp of chat creation |
created_by_id |
|
User that created the chat, place holder, currently always |
id |
|
ID of Chat Session |
messages |
|
Array with all messages of chat |
name |
|
Name agent set for chat user, if applicable |
preferences |
|
Various internal Meta data of the session_id |
session_id |
|
Unique Session ID |
state |
|
Current state of chat session |
tags |
|
Tags applied to Chat Session by agent, if applicable |
updated_at |
|
Last update |
updated_by_id |
|
User ID that last updated session, may be |
user |
#{user object} |
Complete Payload of the chat agemt
Please see User for more
|
user_id |
|
User ID of chat agent |
Install with Docker Compose¶
Warning
We currently do not support Docker environments in productive use. It’s no problem if you run Zammad on docker, however, support is only provided for Zammad as application!
Docker Compose environments require deeper system know how. If you’re not too familiar with Docker and the way it works, you may want to stick with the package installation instead.
Docker is a container-based software framework for automating deployment of applications. Compose is a tool for defining and running multi-container Docker applications.
Zammads docker images are hosted on Dockerhub.
Hint
By default, docker compose will use a fixed Zammad version like
6.2.0-1
, which refers to a specific commit. In this scenario,
you are responsible to apply updates by updating the version on your own.
Alternatively, you can also use floating versions that will give you automatic updates
via docker compose pull
:
# VERSION=6.2 # all patchlevel updates
# VERSION=6 # including minor updates
# VERSION=latest # all updates of stable versions, including major
# VERSION=develop # bleeding-edge development version (not recommended for production use)
Before you start, make sure to have at least 4 GB of RAM to run the containers.
Install Docker Environment¶
This documentation expects you already have a working Docker Compose environment. You can find the required documentations for these steps below:
Getting started with zammad-docker-compose¶
Docker Compose Environment Variables¶
Zammad’s Docker Compose supports several environment variables that are not
set by default. The best way to provide these is within the file .env
.
In case our default docker-compose.yml
is not good enough, please use
docker-compose.override.yml
to provide own changes.
Note
Unless stated otherwise, below environment variables count for the whole Zammad stack and not single containers. Below grouping is to help you find them better, but do not reflect their container.
Docker Compose¶
Variable |
Default Value |
Description |
Additional Info |
---|---|---|---|
RESTART |
|
By default containers will be restarted in case they stopped for whatever reason. |
|
VERSION |
This variables contains the version tag. Example: |
We update this string from time to time, Docker Hub may contain more current tags to the moment you’re pulling. |
Zammad¶
Variable |
Default Value |
Description |
Additional Info |
---|---|---|---|
AUTOWIZARD_JSON |
|
This variable allows you to provide initial configuration data for your instance. Autowizard JSON is out of scope of this documentation, however this example file should help. |
Tip This variable is specific to the |
ZAMMAD_WEB_CONCURRENCY |
|
Allows spawning |
Tip This variable is specific to the |
ZAMMAD_SESSION_JOBS
_CONCURRENT
|
|
Allows spawning |
Tip This variable is specific to the |
ZAMMAD_PROCESS_SCHEDULED
_JOBS_WORKERS
|
|
Allows spawning |
Tip This variable is specific to the |
ZAMMAD_PROCESS_DELAYED
_JOBS_WORKERS
|
|
Allows spawning |
Tip This variable is specific to the |
RAILS_TRUSTED_PROXIES |
|
By default Zammad trusts localhost proxies only. |
Tip This variable is specific to the Danger ⚠ Only change this option if you know what you’re doing! ⚠ |
Elasticsearch¶
Variable |
Default Value |
Description |
Additional Info |
---|---|---|---|
ELASTICSEARCH_ENABLED |
|
Setting this variable to false will allow you to run your Zammad without Elasticsearch. Please note that we strongly advise against doing so. |
Tip This variable is specific to the |
ELASTICSEARCH_HOST |
|
Provide a host name or address to your external Elasticsearch cluster. |
Tip This variable is specific to the |
ELASTICSEARCH_PORT |
|
Provide a different port for Elasticsearch if needed. |
Tip This variable is specific to the |
ELASTICSEARCH_SCHEMA |
|
By default Elasticsearch is reachable via HTTP. |
Tip This variable is specific to the |
ELASTICSEARCH_NAMESPACE |
|
With this name space all Zammad related indexes will be created. Change this if you’re using external clusters. |
Tip This variable is specific to the |
ELASTICSEARCH_REINDEX |
|
By default the docker-compose will always re-index upon a restart. On big installations this may be troublesome. |
Warning Disabling this setting requires you to re-index your search index manually whenever that’s needed by upgrading to a new Zammad version! Tip This variable is specific to the |
ELASTICSEARCH_SSL_VERIFY |
|
Allows you to let the compose scripts ignore self signed SSL certificates for your Elasticsearch installation if needed. |
Tip This variable is specific to the |
Memcached¶
Variable |
Default Value |
Description |
Additional Info |
---|---|---|---|
MEMCACHE_SERVERS |
|
Provide your own Memcached instance if you already have one existing. |
Warning Was |
Redis¶
Variable |
Default Value |
Description |
Additional Info |
---|---|---|---|
REDIS_URL |
|
Provide your own Redis instance if you already have one. |
Warning This method currently does not allow authentication. |
NGINX¶
Variable |
Default Value |
Description |
Additional Info |
---|---|---|---|
NGINX_PORT |
|
The port Nginx will listen on. |
Tip This variable is specific to the |
NGINX_SERVER_NAME |
|
By default the Nginx container of Zammad will respond to all request. You can provide your IP / FQDN if you want to. |
Tip This variable is specific to the |
NGINX_SERVER_SCHEME |
|
If the Nginx container for Zammad is not the upstream server (aka you’re using another proxy in front of nginx) |
Tip This variable is specific to the |
ZAMMAD_RAILSSERVER_HOST |
|
Host name of the rails server container. |
|
ZAMMAD_RAILSSERVER_PORT |
|
Port of Zammads rails server. |
Please also note Configuration via Environment Variables in this regard. |
ZAMMAD_WEBSOCKET_HOST |
|
Host name of Zammads websocket server. |
Tip This variable is specific to the |
ZAMMAD_WEBSOCKET_PORT |
|
Port of Zammads websocket server. |
Please also note Configuration via Environment Variables in this regard. |
Tip
😖 Can’t login because of CSRF token errors?
This usually affects systems with more than one proxy server only. For this to function you may have to tell your web server directly which connection type was used.
Warning
Do not use below options if you’re unsure, they may technically be a security issue!
The following options expect HTTPs connections which should be your goal.
Within your virtual host configuration, locate both directives
proxy_set_header X-Forwarded-Proto
and replace $scheme
by
https
.
Within your virtual host configuration just above the first
ProxyPass
directive insert:
RequestHeader set X_FORWARDED_PROTO 'https'
RequestHeader set X-Forwarded-Ssl on
PostgreSQL¶
Variable |
Default Value |
Description |
Additional Info |
---|---|---|---|
POSTGRESQL_HOST |
|
Host name of your PostgreSQL server. Use your own if you already have one. |
|
POSTGRESQL_PORT |
|
Adjust the Port of your PostgreSQL server. |
|
POSTGRESQL_USER |
|
The database user for Zammad. |
|
POSTGRESQL_PASS |
|
The password of Zammads database user. |
|
POSTGRESQL_DB |
|
Zammads database to use. |
|
POSTGRESQL_OPTIONS |
|
Additional postgresql params to be appended to the database URI. |
|
POSTGRESQL_DB_CREATE |
|
By default we will create the required database. |
Note On own database servers this setting might be troublesome. Tip This variable is specific to the |
Step 1: Clone GitHub repo¶
Warning
If you’re updating Zammad, below commands will cause values set in .env
and docker-compose.override.yml
to be lost. You’re expected to check
if the docker-compose.yml
has changed and if so to adjust it accordingly.
$ git clone https://github.com/zammad/zammad-docker-compose.git
$ cd zammad-docker-compose
Hint
If cloning is too much of a hassle, you can also download the files from https://github.com/zammad/zammad-docker-compose/releases. This will make sure file permissions are preserved.
Step 2: Setting vm.max_map_count for Elasticsearch¶
Even with running Elasticsearch in a container, you’re required to adjust your host’s settings to ensure a clean runtime.
$ sysctl -w vm.max_map_count=262144
Step 3: Adjust Environment as needed¶
In some cases our default environment is not what a docker-compose user is looking for. To remove complexity from this page, we outsourced information on this topic.
Step 4: Start Zammad using DockerHub images¶
Warning
Before starting your containers ensure to not use default login data for your Zammad database! See Step 3!
$ docker compose up -d
Hint
🔧 How to run rails/rake commands in containers
The docker entrypoint script sets up environment variables required by Zammad to function properly.
That is why calling rails
/ rake
on the console should be done via one of the following methods:
$ docker compose run --rm zammad-railsserver rails r '...your rails command here...'
This will run the command via the docker entrypoint and is recommended. In case you require the use of docker exec
, you can use the following command:
$ docker exec zammad-docker-compose-zammad-railsserver-1 /docker-entrypoint.sh rails r '...your rails command here...'
This will manually invoke the docker entrypoint and pass the desired command to it for execution in the proper environment.
Next steps¶
With this Zammad technically is ready to go. However, you’ll need to follow the following further steps to access Zammads Web-UI and getting started with it.
You may also find Zammads Console commands useful
If you expect usage with 5 agents or more you may also want to consider the following pages.
Install on Kubernetes via Helm¶
Warning
We currently do not support Kubernetes installations in productive use.
Kubernetes (k8s) is an open-source system for automating deployment, scaling, and management of containerized applications.
For maintainability reasons, this documentation does not cover the installation instructions because these are included in our helm chart anyway. You can find the helm chart in the zammad-helm repository here.
Updating Zammad¶
Note
- 🙈 Better safe than sorry
Before updating to a new version, please have a look into the release notes. These will provide further information on new feature and fixes, but also technical remarks that may be relevant during an upgrade!
- 🤓 What about Zammad upgrade paths…?
In general we do not encourage you to skip Zammad versions or have long update cycles. Zammad potentially stores very sensitive information (personal information) which is why updating is very important.
If you don’t have time for updating all the time (nobody got time for that, right?), please consider using Zammad hosting for your and your customers’ safety.
In case you couldn’t update for a longer time, please ensure to at least update from major to major version. Big version jumps may work but usually go terribly wrong. As example, expecting the current stable version of Zammad being 5.1 and your instance being on Zammad 2.4, your path would look like so:
2.4
→3.0
→4.0
→5.0
→latest stable (5.1)
- Step 1: Ensure dependencies
Before proceeding, double-check that your system environment matches Zammad’s requirements.
- Step 2: Stop Zammad
$ systemctl stop zammad
- Step 3: Backup Zammad
See Backup and Restore for more information.
- Step 4: Clear Zammad cache
$ zammad run rails r "Rails.cache.clear"
- Step 5: Update Zammad
$ apt update $ apt upgrade
$ yum update zammad
$ zypper ref $ zypper up
Warning
The package comes with maintenance scripts that will run regular tasks during updates for you.
HoweverDo not run Zammad updates unattended and always have a look on the outputs these helper scripts generate. Ignoring said output may lead to incomplete updates that may corrupt data or lead to issues you find way too late.- Step 6: Run required extra steps
Extra steps needed for updates are mentioned in our release news.
Updating Elasticsearch may be relevant in this step.
- Step 7: Log into Zammad
Yes, that’s it!
Danger
Zammad’s former scheduler.rb
script has changed and is now called
background-worker.rb
. Please ensure to reinstall the service - see
Step 6: Start Zammad or install as service!
- Step 1: Ensure dependencies
Before proceeding, double-check that your system environment matches Zammad’s requirements.
- Step 2: Stop Zammad and Clear Zammad cache
Before you continue, stop your Zammad processes.
$ rails r "Rails.cache.clear"
- Step 3: Download Zammad to your system
Get the latest stable release of Zammad here. This file will be updated whenever new bug-fixes are applied, so you can update from this URL regularly.
$ cd /opt $ wget https://ftp.zammad.com/zammad-latest.tar.gz $ tar -xzf zammad-latest.tar.gz --strip-components 1 -C zammad $ chown -R zammad:zammad zammad/ $ rm -f zammad-latest.tar.gz
For security reasons, ensure that your database configuration is readable for the Zammad user only.
$ chmod 600 /opt/zammad/config/database.yml $ chown zammad:zammad /opt/zammad/config/database.yml
- Step 4: Install Gems
$ su - zammad $ cd /opt/zammad $ gem install bundler
$ bundle install --without test development mysql
$ bundle install --without test development postgres
Danger
Support for MySQL/MariaDB will be dropped in Zammad 7.0 upwards. Make sure to migrate your existing instance of Zammad to PostgreSQL.
- Step 5: Stop Zammad services
Stop the application server, websocket server and scheduler.
- Step 6: Upgrade your database
$ su - zammad $ rake db:migrate $ rake assets:precompile
- Step 7: Synchronize Zammad’s translation files
$ su - zammad # ignore if you haven't exited the Zammad user $ rails r "Locale.sync" $ rails r "Translation.sync"
- Step 8: Start Zammad services
Start the application server, web socket server and scheduler.
- Step 9: Log into Zammad
Yes, that’s it!
Warning
⚠️ Updates may require extra steps or introduce breaking changes.
Always check the upgrade notes first.
Note
🙀 Incomplete documentation
Sorry, but this documentation part is outdated. We will rework this part later, but can’t tell when yet.
Please feel welcome to provide a pull request if you find spare time!
$ docker-compose stop
$ git pull
$ docker-compose pull
$ docker-compose up
Start Zammad building Docker images locally with development branch
GIT_BRANCH=develop docker-compose -f docker-compose-build.yml up
Recreate locally built images
GIT_BRANCH=develop docker-compose -f docker-compose-build.yml build –no-cache
Open shell in running Zammad image
docker-compose exec zammad /bin/bash
Port compatibility error
The nginx container may have compatibility problems with other machines or services pointing to port 0.0.0.0:80. So to fix this, we’ll just have to modify the file docker-compose.override.yml and select different ports
Updating Elasticsearch¶
Warning
Updating Elasticsearch does not automatically update it’s plugins! This usually isn’t an issue if Zammad is being updated right after Elasticsearch.
If you want to upgrade your elasticsearch installation, please take a look at the elasticsearch documentation as it will have the most current information for you.
If, for whatever reason, you need to rebuild your search index after upgrading, use:
$ zammad run rake zammad:searchindex:rebuild
Optionally, you can specify a number of CPU cores which are used for rebuilding the searchindex, as in the following example with 8 cores:
$ zammad run rake zammad:searchindex:rebuild[8]
Hint
🤓 Zammad 5.2 comes with changes
As of Zammad 5.2 the reindex command has changed! You will still be able to use the old method until Zammad 6, however, will receive a deprecation warning.
Warning
This step may fail if Zammad is under heavy load: Elasticsearch locks the indices from deletion if you’re pumping in new data, like receiving a new ticket. (This only applies to single-node deployments, not clusters.)
If it does, try killing Zammad first:
$ systemctl stop zammad
$ zammad run rake zammad:searchindex:rebuild
$ systemctl start zammad
Configure the webserver¶
You can find current sample configuration files for your webserver within
contrib/
of your Zammad installation.
If you’re using the package installation, Zammad attempts to automatically install a configuration file to your nginx for you.
Note
The Zammad installation will not automatically set any host- or server name for you.
Get a ssl certificate (recommended)¶
Don’t know how to get SSL certificates and install them on a webserver yet? The guide within the tabs below can help you jumping in.
Make sure to used named configuration. The default sample configuration for both nginx and apache are not named.
To fix this, open the zammad.conf
in your webservers configuration
directory and make sure to replace server_name localhost;
(nginx) or
ServerName localhost
(Apache 2) with Zammad’s actual subdomain.
Where?
nginx:
/etc/nginx/conf.d/
/etc/nginx/vhosts.d/
/etc/nginx/sites-available/
Apache 2:
/etc/apache2/conf.d/
/etc/httpd/vhosts.d/
/etc/apache2/sites-available/
You either already know what you’re doing, you’re developing or like the danger. ⚔️
letsencrypt is an easy and free way to retreive valid ssl certificates. These certificates are valid for 90 days and can be renewed automatically.
The two most common tools are certbot and acme.sh.
If not happened automatically, you have to install
the nginx or apache plugin for certbot:
python3-certbot-nginx
OR python3-certbot-apache
During the first certbot run it will request additional information
once. Replace <webserver>
in below command by either
apache
, httpd
or nginx
and to match your setup.
$ certbot --<webserver> -d zammad.example.com
Certbot will now attempt to issue a certificate for you.
If successful, certbot will ask you if you want to
[1] not redirect
or [2] redirect
automatically.
You can choose to not redirect if you plan to use the sample
configuration of Zammad. If not, select [2] redirect
.
From this moment on, certbot will automatically renew your installed certificates if they’re valid for another 30 days or less.
Hint
Not exactly what you’re looking for?
The cerbot documentation has a lot more use cases than we cover here.
Note
acme.sh by default no longer uses letsencrypt. For this reason you’ll have to change the default CA.
$ acme.sh --set-default-ca --server letsencrypt
If you want to use any other CA with acme.sh, consult their documentation on how to.
First of all you’ll need to issue your certificate.
acme.sh will save this certificate to
/root/.acme.sh/<your-domain>/
Replace <webserver>
in the following command by either
apache
or nginx
and to match your setup, use standalone
for other webservers.
$ acme.sh --issue --<webserver> -d zammad.example.com
It’s not recommended to use the just stored certificates directly. Instead you should install the certificate to a directory of your choice.
We’re using /etc/ssl/private/
in this case, but you can use any
directory you like.
Warning
Ensure to adjust value for --reloadcmd
as this will
ensure that acme.sh reloads your webserver automatically
after getting a renewal. Replace <webserver>
by either
apache2
, httpd
or nginx
$ acme.sh --install-cert -d zammad.example.com \
--cert-file /etc/ssl/private/zammad.example.com.pem \
--key-file /etc/ssl/private/zammad.example.com.key \
--fullchain-file /etc/ssl/private/zammad.example.com.full.pem \
--reloadcmd "systemctl force-reload <webserver>"
From this moment on, acme.sh will automatically renew your installed certificates if they’re valid for another 30 days or less.
Hint
Not exactly what you’re looking for?
The acme.sh documentation has a lot more use cases than we cover here.
If you prefer to use certificates from other official CAs than letsencrypt, you can do so as well. Just get your certificate bundle from the source you prefer and continue with Adjusting the webserver configuration.
Note
🙋 I’m new to SSL certificates. Where can I get a certificate?
The easiest way to get certificates is to buy an annual subscription through a commercial CA, such as:
(Zammad is not affiliated with these CAs in any way.)
Another way is to use self signed certificates from your own CA. In general you shouldn’t use this option when you have users accessing Zammad that can’t verify your certificates.
Beside creating own certificates via e.g. XCA or Microsoft CA, you can also generate a certificate really quick like so:
On any system with openssl
installed, you can run below command.
Provide the requested information and ensure to provide the fqdn of
Zammad when being asked for
Common Name (e.g. server FQDN or YOUR name)
.
$ openssl req -newkey rsa:4096 -nodes -x509 -days 1825\
-keyout key.pem -out certificate.pem
Above command creates a certificate that’s valid for 5 years. It will write the certificate and private key to the current directory you’re in. If you want to check your certificate you just created, you can use the following command.
$ openssl x509 -text -noout -in certificate.pem
Hint
Not good enough for you?
If above command is not good enough for you, the openSSL documentation is a good place to learn more.
Adjusting the webserver configuration¶
Warning
For a quick start, we’re installing a HTTP configuration. You should never use HTTP connections for authentication - instead, we encourage you to use HTTPS!
If Zammad scripts automatically installed your webserver configuration file, ensure to not rename it. Below we’ll cover HTTPs for above reason.
- Step 1 - Get a current config file
Copy & overwrite the default
zammad.conf
by using$ cp /opt/zammad/contrib/nginx/zammad_ssl.conf /etc/nginx/sites-available/zammad.conf
Your nginx directories may differ, please adjust your commands if needed.
Most common:
/etc/nginx/conf.d/
/etc/nginx/vhosts.d/
/etc/nginx/sites-available/
- Step 2 - Adjust the config file
Adjust the just copied file with a text editor of your choice (e.g.
vi
ornano
).Locate any
server_name
directive and adjustexample.com
to the subdomain you have chosen for your Zammad instance.Now you’ll need to adjust the path and file names for your ssl certificates your obtained on the prior steps. Adjust the following directives to match your setup:
ssl_certificate
(your ssl certificate)ssl_certificate_key
(the certificates private key)ssl_trusted_certificate
(the public CA certificate)
Note
Technically this is not a hard requirement, but recommended!
Hint
🤓 Don’t have a dhparam.pem file yet?
You can easily adapt below example to generate this file. It will improve HTTPs security and thus should be used.
You can find the path by looking at your webserver configuration by looking for:
ssl_dhparam
directive (nginx)SSLOpenSSLConfCmd DHParameters
directive (apache2)
$ openssl dhparam -out <path>/dhparam.pem 4096
- (Optional) - Adjust HTTPs configuration
Our default configuration aims for a broad support of enduser devices. This may not fit your needs - Mozilla has a great ssl-config generator that should help you to meet your requirements!
- Step 3 - Save & reload
Reload your nginx
systemctl reload nginx
to apply your configuration changes.
- Step 1 - Ensure required modules are enabled
Zammad requires modules that are not enabled by default. By default use
a2enmod
(not CentOS) to do so.$ a2enmod proxy proxy_html proxy_http proxy_wstunnel headers ssl $ systemctl restart apache2
add/uncomment the appropriate
LoadModule
statements in your Apache config:# /etc/httpd/conf/httpd.conf LoadModule headers_module modules/mod_headers.so LoadModule proxy_module modules/mod_proxy.so LoadModule proxy_html_module modules/mod_proxy_html.so LoadModule proxy_http_module modules/mod_proxy_http.so LoadModule proxy_wstunnel_module modules/mod_proxy_wstunnel.so
Don’t forget to restart your apache.
- Step 2 - Get a current config file
Note
Package installations attempt to copy a
zammad.conf
to your webservers configuration directory. Do not rename this file!Copy & overwrite the default
zammad.conf
by using$ cp /opt/zammad/contrib/apache2/zammad_ssl.conf /etc/apache2/sites-available/zammad.conf
Your apache directories may differ, please adjust your commands if needed.
Most common:
/etc/apache2/conf.d/
/etc/httpd/vhosts.d/
/etc/apache2/sites-available/
- Step 3 - Adjust the config file
Adjust the just copied file with a text editor of your choice (e.g.
vi
ornano
).Locate any
ServerName
directive and adjustexample.com
to the subdomain you have chosen for your Zammad instance.Now you’ll need to adjust the path and file names for your ssl certificates your obtained on the prior steps. Adjust the following directives to match your setup:
SSLCertificateFile
(your ssl certificate)SSLCertificateKeyFil
(the certificates private key)SSLCertificateChainFile
(the public CA certificate)
Note
Technically this is not a hard requirement, but recommended!
Hint
🤓 Don’t have a dhparam.pem file yet?
You can easily adapt below example to generate this file. It will improve HTTPs security and thus should be used.
You can find the path by looking at your webserver configuration by looking for:
ssl_dhparam
directive (nginx)SSLOpenSSLConfCmd DHParameters
directive (apache2)
$ openssl dhparam -out <path>/dhparam.pem 4096
- (Optional) - Adjust HTTPs configuration
Our default configuration aims for a broad support of enduser devices. This may not fit your needs - Mozilla has a great ssl-config generator that should help you to meet your requirements!
- (Optional) - Enable the site
Hint
This step mostly depends on your selected folders and most often only affects
sites-available
folders.$ a2ensite zammad
$ ln -s /etc/httpd/sites-available/zammad_ssl.conf /etc/httpd/sites-enabled/
Also, make sure the following line is present in your Apache configuration:
# /etc/apache2/apache2.conf (Ubuntu, Debian, & openSUSE) # /etc/httpd/conf/httpd.conf (CentOS) IncludeOptional sites-enabled/*.conf
- Step 4 - Save & reload
Reload your apache
systemctl reload apache2
to apply your configuration changes.
Want to test locally first or use a different Proxy we don’t support?
The main application (rails server) is listening on
http://127.0.0.1:3000
.
If you’re using a proxy server, also ensure that you proxy the websockets
as well. The websocket server listens on ws://127.0.0.1:6042
.
If above ports are used by other applications already, please have a look at network options on our environment page.
Warning
Do not expose Zammad directly to the internet, as Zammad only provides HTTP!
If you just installed Zammad, you’ll be greeted by our getting started wizard. 🙌 You now can continue with First steps.
Hint
You’re not seeing Zammads page but a default landing page of your OS?
Ensure that you did restart your webserver - also check if
000-default.conf
or default.conf
in your vhost directory
possibly overrules your configuration.
Sometimes this is also a DNS resolving issue.
Tip
😖 Can’t login because of CSRF token errors?
This usually affects systems with more than one proxy server only. For this to function you may have to tell your web server directly which connection type was used.
Warning
Do not use below options if you’re unsure, they may technically be a security issue!
The following options expect HTTPs connections which should be your goal.
Within your virtual host configuration, locate both directives
proxy_set_header X-Forwarded-Proto
and replace $scheme
by
https
.
Within your virtual host configuration just above the first
ProxyPass
directive insert:
RequestHeader set X_FORWARDED_PROTO 'https'
RequestHeader set X-Forwarded-Ssl on
First steps¶
After successfully installing Zammad you’ll have a couple of options.
Start from scratch (move on to the next section)
Getting Started Wizard¶
If you visit Zammad’s web page the first time, you’ll be greeted by its Getting Started Wizard. It will guide you through the first most important things.
- Step 1: Create your very first administrator account
The fields should be fairly self explaining.
Zammad does require the following password security by default:
Password length of 10 or more
2 upper and 2 lower characters
contains at least one digit
- Step 2: Provide company information
You can upload a custom logo fitting to your company here. The instance address is detected automatically and only required adjustment in case it’s detected wrong.
All of these settings can be changed within Branding and System settings.
- Step 3: E-Mail notification channel
By default Zammad uses sendmail - if that doesn’t fit you can change it to SMTP here.
Zammad uses
noreply@<your-fqdn>
as sender address by default. SMTP setups might fail - you may want to skip this step with choosingsendmail
at this point. You can adjust it later!- Step 4: Your first email channel (optional)
If you want to start right away, you can connect your email account already.
Warning
Zammad reacts to fetched emails by default. If that’s not what you want, skip this step for now.
Learn more about the email channel within the documentation for email channels.
After finishing the wizard you’ll be automatically logged in to the just created account.
Further Steps¶
In our opinion the next step order would like below sample. You can skip parts you don’t need or adapt. All parts are described within Zammad’s admin documentation.
Configure your required groups
Adjust triggers as needed
Add postmaster filters if needed
Configure SLAs if needed
add email / social media channels & signatures(go back to group settings to add outgoing email addresses)Add Text Modules
Add Organizations
Configure roles if needed
Consider Third Party logins or LDAP integration for easier logins
Add agent accounts (users)
Consider backup strategies for Zammad, see Backup and Restore
From point 5 on you’ll be able to work productive in theory. 🙌
Hint
😖 Are you still lost?
If above list doesn’t help you or you’ll need to jump in a lot faster, you can also get Workshops with one of our Zammad consultants.
Migrating to Zammad¶
Zammad will migrate the following information:
Tickets and their Articles
Groups / Queues
Organizations
Agents and Customers (if applicable)
After migrating to Zammad you’ll want to continue with the First steps to configure Zammad. This has to be done after migration.
Limitations¶
There might be source dependent limitations which we will be covering on the direct migration pages.
However, these limitations count for all migrations:
Migrations are only possible on new instances.
Migrations are only possible from one sources. Several migration sources on one instance are not supported.
Zammad can’t migrate object types it doesn’t know, migrations will fail.
Zammad migrates all or nothing. This means that you can’t deselect specific information specific groups, tickets or users.
Available Migration Options¶
From Freshdesk¶
Limitations¶
Please note below Freshdesk specific limitations. These are additional limitations to the general ones listed.
Differential migrations are not supported!The general suggestion is to run a test import before to learn how long the migration is going to take.Important: Please note that migration speed highly depends on your Freshdesk plan (API rate limits apply).
Due to API limitations Zammad will not show the total number of objects to import, but instead correct them in steps of
100
.Your Freshdesk plan has to provide API support. This may not apply to all available plans.
User passwords are not migrated and will require the user to use the password reset link on the login page.
Prerequisites¶
Zammad requires API access which is why you’ll need to create an API key for the migration. The migrator will request your Freshdesk subdomain and API key.
Warning
Ensure to retrieve the API key with a full administrator account. Less privileged users will end in a broken migration.
Importing Freshdesk data¶
Generally you have two options on how to migrate data. If you have a fairly big instance with a lot of data, you may want to consider using the console over the browser version.
After installing Zammad and configuring your webserver, navigate to your Zammads FQDN in your browser and follow the migration wizard.
Depending on the number of users, tickets and Freshdesk plan this may take some while.

Note
😖 Scheduler got interrupted

If this message appears after providing your credentials, please be patient. The migration should start within 5 minutes.
If you receive above message after the migration begun, please consider using the console approach instead and reset the installation.
- Open a console:
# package installation $ zammad run rails c # source installation $ rails c
Learn more about the Zammad console.
- To prepare the migration, run the following commands
# Set variables for easier settings $ subdomain = '{freshdesk subdomain}.freshdesk.com' $ token = '{freshdesk token}' # Update Zammad settings for freshdesk import $ Setting.set('import_freshdesk_endpoint', "https://#{subdomain}/api/v2") $ Setting.set('import_freshdesk_endpoint_key', token) $ Setting.set('import_backend', 'freshdesk') $ Setting.set('import_mode', true)
If you want to know if your configuration works in a dry run, run the following command:
Sequencer.process('Import::Freshdesk::ConnectionTest')
- To start the actual migration, run the following commands
# That the actual job $ job = ImportJob.create(name: 'Import::Freshdesk') $ AsyncImportJob.perform_later(job)
Tip
🤓 Want to check the state of the migration?
Running the following command in a rails console will provide detailed state information of your migration.
pp ImportJob.find_by(name: 'Import::Freshdesk')
To give you an idea how the migration job state looks like, you can use below tabs. As long as
finished_at
isnil
, the process is still running.#<ImportJob:0x0000000008274310 id: 1, name: "Import::Freshdesk", dry_run: false, payload: {}, result: {"Organizations"=> {"skipped"=>0, "created"=>0, "updated"=>0, "unchanged"=>0, "failed"=>0, "deactivated"=>0, "sum"=>0, "total"=>100}}, started_at: Mon, 03 Jan 2022 18:41:51 UTC +00:00, finished_at: nil, created_at: Mon, 03 Jan 2022 18:41:16 UTC +00:00, updated_at: Mon, 03 Jan 2022 18:43:32 UTC +00:00>
#<ImportJob:0x000055ba3d9dbbb8 id: 1, name: "Import::Freshdesk", dry_run: false, payload: {}, result: {"Groups"=> {"skipped"=>0, "created"=>3, "updated"=>0, "unchanged"=>0, "failed"=>0, "deactivated"=>0, "sum"=>3, "total"=>3}, "Organizations"=> {"skipped"=>0, "created"=>193, "updated"=>1, "unchanged"=>0, "failed"=>0, "deactivated"=>0, "sum"=>194, "total"=>194}, "Users"=> {"skipped"=>0, "created"=>3352, "updated"=>0, "unchanged"=>0, "failed"=>0, "deactivated"=>0, "sum"=>3352, "total"=>3352}, "Tickets"=> {"skipped"=>0, "created"=>987, "updated"=>0, "unchanged"=>0, "failed"=>0, "deactivated"=>0, "sum"=>987, "total"=>1000}}, started_at: Tue, 04 Jan 2022 11:37:38 UTC +00:00, finished_at: nil, created_at: Tue, 04 Jan 2022 11:37:36 UTC +00:00, updated_at: Tue, 04 Jan 2022 12:12:52 UTC +00:00>
#<ImportJob:0x0000561da0def350 id: 1, name: "Import::Freshdesk", dry_run: false, payload: {}, result: {"Groups"=> {"skipped"=>0, "created"=>3, "updated"=>0, "unchanged"=>0, "failed"=>0, "deactivated"=>0, "sum"=>3, "total"=>3}, "Organizations"=> {"skipped"=>0, "created"=>193, "updated"=>1, "unchanged"=>0, "failed"=>0, "deactivated"=>0, "sum"=>194, "total"=>194}, "Users"=> {"skipped"=>0, "created"=>3352, "updated"=>0, "unchanged"=>0, "failed"=>0, "deactivated"=>0, "sum"=>3352, "total"=>3352}, "Tickets"=> {"skipped"=>0, "created"=>4714, "updated"=>0, "unchanged"=>0, "failed"=>1, "deactivated"=>0, "sum"=>4715, "total"=>4715}}, started_at: Tue, 04 Jan 2022 11:37:38 UTC +00:00, finished_at: Tue, 04 Jan 2022 14:30:57 UTC +00:00, created_at: Tue, 04 Jan 2022 11:37:36 UTC +00:00, updated_at: Tue, 04 Jan 2022 14:30:57 UTC +00:00>
- After the import has finished, run the following commands
$ Setting.set('import_mode', false) $ Setting.set('system_init_done', true) $ Rails.cache.clear
After migration¶
As the migration technically skips the getting started wizard, please note that you want to adjust your FQDN settings (FQDN & HTTP-Type).
Hint
How to log in?
Zammad provides admin access to the user whose API token you provided. Use the admins email address and API token provided during the migration to login.
All other users will have to use the password reset function or login methods like LDAP or one click logins.
After successfully migrating your Freshdesk instance, continue with First steps.
Restarting from scratch¶
Turned wrong at some point? You can find the required commands to reset Zammad in our Dangerzone.
From Kayako¶
Limitations¶
Please note below Kayako specific limitations. These are additional limitations to the general ones listed.
Differential migrations are not supported!The general suggestion is to run a test import before to learn how long the migration is going to take.Selfhosted installations (Kayako classic) are not supported.
The following ticket field customizations are being ignored (affects “Scale” plan):
Custom ticket states,
Custom ticket priorities, and
Custom ticket types.
Important: Please note that migration speed highly depends on your Kayako plan (API rate limits apply).
Your Kayako plan has to provide API support. This may not apply to all available plans.
User passwords are not migrated and will require the user to use the password reset link on the login page.
Prerequisites¶
Zammad requires API access which is why the migrator will request your Kayako-URL, email address and password.
Warning
Ensure to provide an user account with full administrative permissions. Less privileged users will end in a broken migration.
Importing Kayako data¶
Generally you have two options on how to migrate data. If you have a fairly big instance with a lot of data, you may want to consider using the console over the browser version.
After installing Zammad and configuring your webserver, navigate to your Zammads FQDN in your browser and follow the migration wizard.
Depending on the number of users, tickets and Kayako plan this may take some while.

Note
😖 Scheduler got interrupted

If this message appears after providing your credentials, please be patient. The migration should start within 5 minutes.
If you receive above message after the migration begun, please consider using the console approach instead and reset the installation.
- Open a console:
# package installation $ zammad run rails c # source installation $ rails c
Learn more about the Zammad console.
- To prepare the migration, run the following commands
# Set variables for easier settings $ subdomain = '{kayako subdomain}.kayako.com' $ email = '{kayako admin email address}' $ password = '{kayako admin password}' # Update Zammad settings for Kayako import $ Setting.set('import_kayako_endpoint', "https://#{subdomain}/api/v1") $ Setting.set('import_kayako_endpoint_username', email) $ Setting.set('import_kayako_endpoint_password', password) $ Setting.set('import_backend', 'kayako') $ Setting.set('import_mode', true)
If you want to know if your configuration works in a dry run, run the following command:
Sequencer.process('Import::Kayako::ConnectionTest')
- To start the actual migration, run the following commands
# That the actual job $ job = ImportJob.create(name: 'Import::Kayako') $ AsyncImportJob.perform_later(job)
Tip
🤓 Want to check the state of the migration?
Running the following command in a rails console will provide detailed state information of your migration.
pp ImportJob.find_by(name: 'Import::Kayako')
To give you an idea how the migration job state looks like, you can use below tabs. As long as
finished_at
isnil
, the process is still running.#<ImportJob:0x0000000008274310 id: 1, name: "Import::Kayako", dry_run: false, payload: {}, result: {"Organizations"=> {"skipped"=>0, "created"=>0, "updated"=>0, "unchanged"=>0, "failed"=>0, "deactivated"=>0, "sum"=>0, "total"=>100}}, started_at: Mon, 03 Jan 2022 18:41:51 UTC +00:00, finished_at: nil, created_at: Mon, 03 Jan 2022 18:41:16 UTC +00:00, updated_at: Mon, 03 Jan 2022 18:43:32 UTC +00:00>
#<ImportJob:0x000055ba3d9dbbb8 id: 1, name: "Import::Kayako", dry_run: false, payload: {}, result: {"Groups"=> {"skipped"=>0, "created"=>3, "updated"=>0, "unchanged"=>0, "failed"=>0, "deactivated"=>0, "sum"=>3, "total"=>3}, "Organizations"=> {"skipped"=>0, "created"=>193, "updated"=>1, "unchanged"=>0, "failed"=>0, "deactivated"=>0, "sum"=>194, "total"=>194}, "Users"=> {"skipped"=>0, "created"=>3352, "updated"=>0, "unchanged"=>0, "failed"=>0, "deactivated"=>0, "sum"=>3352, "total"=>3352}, "Tickets"=> {"skipped"=>0, "created"=>987, "updated"=>0, "unchanged"=>0, "failed"=>0, "deactivated"=>0, "sum"=>987, "total"=>1000}}, started_at: Tue, 04 Jan 2022 11:37:38 UTC +00:00, finished_at: nil, created_at: Tue, 04 Jan 2022 11:37:36 UTC +00:00, updated_at: Tue, 04 Jan 2022 12:12:52 UTC +00:00>
#<ImportJob:0x0000561da0def350 id: 1, name: "Import::Kayako", dry_run: false, payload: {}, result: {"Groups"=> {"skipped"=>0, "created"=>3, "updated"=>0, "unchanged"=>0, "failed"=>0, "deactivated"=>0, "sum"=>3, "total"=>3}, "Organizations"=> {"skipped"=>0, "created"=>193, "updated"=>1, "unchanged"=>0, "failed"=>0, "deactivated"=>0, "sum"=>194, "total"=>194}, "Users"=> {"skipped"=>0, "created"=>3352, "updated"=>0, "unchanged"=>0, "failed"=>0, "deactivated"=>0, "sum"=>3352, "total"=>3352}, "Tickets"=> {"skipped"=>0, "created"=>4714, "updated"=>0, "unchanged"=>0, "failed"=>1, "deactivated"=>0, "sum"=>4715, "total"=>4715}}, started_at: Tue, 04 Jan 2022 11:37:38 UTC +00:00, finished_at: Tue, 04 Jan 2022 14:30:57 UTC +00:00, created_at: Tue, 04 Jan 2022 11:37:36 UTC +00:00, updated_at: Tue, 04 Jan 2022 14:30:57 UTC +00:00>
- After the import has finished, run the following commands
$ Setting.set('import_mode', false) $ Setting.set('system_init_done', true) $ Rails.cache.clear
After migration¶
As the migration technically skips the getting started wizard, please note that you want to adjust your FQDN settings (FQDN & HTTP-Type).
Hint
How to log in?
Zammad provides admin access to the user whose login credentials you provided. Use the admins email address and password provided during the migration to login.
All other users will have to use the password reset function or login methods like LDAP or one click logins.
After successfully migrating your Kayako instance, continue with First steps.
Restarting from scratch¶
Turned wrong at some point? You can find the required commands to reset Zammad in our Dangerzone.
Migration from OTRS¶
Limitations¶
Please note below OTRS specific limitations. These are additional limitations to the general ones listed.
Password migration works for OTRS >= 3.3 only(on older instances a password reset within Zammad will be required)If you plan to import a differential migration after, do not change any data in Zammad!
Only customers of tickets are imported
Zammad expects your OTRS timestamps to be UTC and won’t adjust them
If you plan to import a differential after, do not change any data in Zammad!
Note
Supported OTRS version: 3.1 up to 6.x
Prerequisites¶
Step 1: Install Znuny4OTRS-Repo¶
This is a dependency of the OTRS migration plugin.
https://ftp.zammad.com/otrs-migrator-plugins/Znuny4OTRS-Repo-6.0.76.opm
https://ftp.zammad.com/otrs-migrator-plugins/Znuny4OTRS-Repo-5.0.56.opm
https://ftp.zammad.com/otrs-migrator-plugins/Znuny4OTRS-Repo-4.0.25.opm
https://ftp.zammad.com/otrs-migrator-plugins/Znuny4OTRS-Repo-3.3.2.opm
Step 2: Install OTRS migration plugin¶
https://ftp.zammad.com/otrs-migrator-plugins/Znuny4OTRS-ZammadMigrator-6.0.7.opm
https://ftp.zammad.com/otrs-migrator-plugins/Znuny4OTRS-ZammadMigrator-5.0.4.opm
https://ftp.zammad.com/otrs-migrator-plugins/Znuny4OTRS-ZammadMigrator-4.1.12.opm
https://ftp.zammad.com/otrs-migrator-plugins/Znuny4OTRS-ZammadMigrator-3.0.33.opm
Hint
In some cases restarting your webserver may help to solve internal server errors.
Importing OTRS data¶
Note
If your OTRS installation is rather huge, you might want to consider using the command line version of this feature. This also applies if you experience Timeouts during the migration.
After installing Zammad and configuring your webserver, navigate to your Zammads FQDN in your Browser and follow the migration wizard.
Depending on the size of your OTRS installation this may take a while.
You can get an idea of this process in the migrator video on vimeo .
- Open a console:
# package installation $ zammad run rails c # source installation $ rails c
Learn more about the Zammad console.
If you miss this at the beginning or you want to re-import again you have to use the command line at the moment.
Stop all Zammad processes and switch Zammad to import mode (no events are fired - e. g. notifications, sending emails, …)
- Start the migration
Ensure to replace xxx with your values.
>> Setting.set('import_otrs_endpoint', 'https://xxx/otrs/public.pl?Action=ZammadMigrator') >> Setting.set('import_otrs_endpoint_key', 'xxx') >> Setting.set('import_mode', true) >> Import::OTRS.start
- After the import has finished, run the following commands
$ Setting.set('import_mode', false) $ Setting.set('system_init_done', true) $ Rails.cache.clear
After successfully migrating your OTRS installation, continue with First steps.
Importing a differential¶
Note
This is only possible after finishing an earlier OTRS import successful.
In some cases it might be desirable to update the already imported data from OTRS. This is possible with the following commands.
- Run a differential import
>> Setting.set('import_otrs_endpoint', 'http://xxx/otrs/public.pl?Action=ZammadMigrator') >> Setting.set('import_otrs_endpoint_key', 'xxx') >> Setting.set('import_mode', true) >> Setting.set('system_init_done', false) >> Import::OTRS.diff_worker
- After the import has finished, run the following commands
$ Setting.set('import_mode', false) $ Setting.set('system_init_done', true) $ Rails.cache.clear
All changes that occurred after your first migration should now also be available within your Zammad installation.
Restarting from scratch¶
Turned wrong at some point? You can find the required commands to reset Zammad in our Dangerzone.
From Zendesk¶
Limitations¶
Please note below Zendesk specific limitations. These are additional limitations to the general ones listed.
Differential migrations are not supported!The general suggestion is to run a test import before to learn how long the migration is going to take.Important: Please note that migration speed highly depends on your Zendesk plan (API rate limits apply).
Your Zendesk plan has to provide API support. This may not apply to all available plans.
User passwords are not migrated and will require the user to use the password reset link on the login page.
Prerequisites¶
Zammad requires API access which is why you’ll need to create an API key for the migration. The migrator will request your Zendesk-URL, email address and API key.
Warning
Ensure to retrieve the API key with a full administrator account. Less privileged users will end in a broken migration.
Importing Zendesk data¶
Generally you have two options on how to migrate data. If you have a fairly big instance with a lot of data, you may want to consider using the console over the browser version.
After installing Zammad and configuring your webserver, navigate to your Zammads FQDN in your browser and follow the migration wizard.
Depending on the number of users, tickets and Zendesk plan this may take some while.

Note
😖 Scheduler got interrupted

If this message appears after providing your credentials, please be patient. The migration should start within 5 minutes.
If you receive above message after the migration begun, please consider using the console approach instead and reset the installation.
- Open a console:
# package installation $ zammad run rails c # source installation $ rails c
Learn more about the Zammad console.
- To prepare the migration, run the following commands
# Set variables for easier settings $ subdomain = '{zendesk url}' $ email = '{zendesk admin email address}' $ token = '{zendesk token}' # Update Zammad settings for Zendesk import $ Setting.set('import_zendesk_endpoint', "https://#{subdomain}/api/v2") $ Setting.set('import_zendesk_endpoint_username', email) $ Setting.set('import_zendesk_endpoint_key', token) $ Setting.set('import_backend', 'zendesk') $ Setting.set('import_mode', true)
If you want to know if your configuration works in a dry run, run the following command:
Sequencer.process('Import::Zendesk::ConnectionTest')
- To start the actual migration, run the following commands
# That the actual job $ job = ImportJob.create(name: 'Import::Zendesk') $ AsyncImportJob.perform_later(job)
Tip
🤓 Want to check the state of the migration?
Running the following command in a rails console will provide detailed state information of your migration.
pp ImportJob.find_by(name: 'Import::Zendesk')
To give you an idea how the migration job state looks like, you can use below tabs. As long as
finished_at
isnil
, the process is still running.#<ImportJob:0x0000000008274310 id: 1, name: "Import::Zendesk", dry_run: false, payload: {}, result: {"Organizations"=> {"skipped"=>0, "created"=>0, "updated"=>0, "unchanged"=>0, "failed"=>0, "deactivated"=>0, "sum"=>0, "total"=>100}}, started_at: Mon, 03 Jan 2022 18:41:51 UTC +00:00, finished_at: nil, created_at: Mon, 03 Jan 2022 18:41:16 UTC +00:00, updated_at: Mon, 03 Jan 2022 18:43:32 UTC +00:00>
#<ImportJob:0x000055ba3d9dbbb8 id: 1, name: "Import::Zendesk", dry_run: false, payload: {}, result: {"Groups"=> {"skipped"=>0, "created"=>3, "updated"=>0, "unchanged"=>0, "failed"=>0, "deactivated"=>0, "sum"=>3, "total"=>3}, "Organizations"=> {"skipped"=>0, "created"=>193, "updated"=>1, "unchanged"=>0, "failed"=>0, "deactivated"=>0, "sum"=>194, "total"=>194}, "Users"=> {"skipped"=>0, "created"=>3352, "updated"=>0, "unchanged"=>0, "failed"=>0, "deactivated"=>0, "sum"=>3352, "total"=>3352}, "Tickets"=> {"skipped"=>0, "created"=>987, "updated"=>0, "unchanged"=>0, "failed"=>0, "deactivated"=>0, "sum"=>987, "total"=>1000}}, started_at: Tue, 04 Jan 2022 11:37:38 UTC +00:00, finished_at: nil, created_at: Tue, 04 Jan 2022 11:37:36 UTC +00:00, updated_at: Tue, 04 Jan 2022 12:12:52 UTC +00:00>
#<ImportJob:0x0000561da0def350 id: 1, name: "Import::Zendesk", dry_run: false, payload: {}, result: {"Groups"=> {"skipped"=>0, "created"=>3, "updated"=>0, "unchanged"=>0, "failed"=>0, "deactivated"=>0, "sum"=>3, "total"=>3}, "Organizations"=> {"skipped"=>0, "created"=>193, "updated"=>1, "unchanged"=>0, "failed"=>0, "deactivated"=>0, "sum"=>194, "total"=>194}, "Users"=> {"skipped"=>0, "created"=>3352, "updated"=>0, "unchanged"=>0, "failed"=>0, "deactivated"=>0, "sum"=>3352, "total"=>3352}, "Tickets"=> {"skipped"=>0, "created"=>4714, "updated"=>0, "unchanged"=>0, "failed"=>1, "deactivated"=>0, "sum"=>4715, "total"=>4715}}, started_at: Tue, 04 Jan 2022 11:37:38 UTC +00:00, finished_at: Tue, 04 Jan 2022 14:30:57 UTC +00:00, created_at: Tue, 04 Jan 2022 11:37:36 UTC +00:00, updated_at: Tue, 04 Jan 2022 14:30:57 UTC +00:00>
- After the import has finished, run the following commands
$ Setting.set('import_mode', false) $ Setting.set('system_init_done', true) $ Rails.cache.clear
After migration¶
As the migration technically skips the getting started wizard, please note that you want to adjust your FQDN settings (FQDN & HTTP-Type).
Hint
How to log in?
Zammad provides admin access to the user whose API token you provided. Use the admins email address and API token provided during the migration to login.
All other users will have to use the password reset function or login methods like LDAP or one click logins.
After successfully migrating your Zendesk instance, continue with First steps.
Restarting from scratch¶
Turned wrong at some point? You can find the required commands to reset Zammad in our Dangerzone.
Note
😖 Missing a migration source?
If we don’t cover your favorite source yet, you’ll have two options. You can either fiddle around by using Zammads powerful API or drop our sales team a message for a custom development or even migrator sponsoring.
🤓 Migrations are available for hosted setups too, contact support for further information!
Console¶
Zammad uses Ruby on Rails so you can make use of the rails console.
Warning
Please double check your commands before running, as some of those commands might cause data loss or damaged tickets! If you’re unsure, use a test system first!
To open the rails console on the shell you have to enter the following commands.
Start Zammad’s Rails console¶
Running a single command¶
The following command will allow you to run a single command, without running a shell (e.g. for automation).
Note
Replace {COMMAND}
with your command you want to run.
Tip
If you enter a p
in front of your command
(e.g. like rails r 'p Delayed::Job.count'
),
you’ll actually receive a printed output (without you won’t!).
# package installation
$ zammad run rails r '{COMMAND}'
# source installation
$ rails r '{COMMAND}'
Running several commands in a shell¶
The following command will provide you a rails console. It allows you to run several commands inside it.
This reduces loading times greatly.
# package installation
$ zammad run rails c
# source installation
$ rails c
Hint
Starting Rails Console in Safe Mode
Normally, starting rails console requires certain third-party services to be up and running. You may receive errors and console will refuse to start in case they are not available.
However, it’s possible to start rails console in safe mode by setting
a special environment variable. With
ZAMMAD_SAFE_MODE=1
set, availability of these services will be ignored.
$ ZAMMAD_SAFE_MODE=1 zammad run rails c
Zammad is running in safe mode. Any third-party services like Redis are ignored.
There was an error trying to connect to Redis via redis://localhost:6379.
Please provide a Redis instance at localhost:6379 or set REDIS_URL to point to a different location.
#<Redis::CannotConnectError: Error connecting to Redis on localhost:6379 (Errno::ECONNREFUSED)>
Loading production environment (Rails 6.1.7.3)
3.1.3 :001 >
$ ZAMMAD_SAFE_MODE=1 rails c
Zammad is running in safe mode. Any third-party services like Redis are ignored.
There was an error trying to connect to Redis via redis://localhost:6379.
Please provide a Redis instance at localhost:6379 or set REDIS_URL to point to a different location.
#<Redis::CannotConnectError: Error connecting to Redis on localhost:6379 (Errno::ECONNREFUSED)>
Loading production environment (Rails 6.1.7.3)
3.1.3 :001 >
Working on the console¶
Here’s a topic list for quick jumping and better overview.
Query and set / update Zammad settings¶
Note
Please note that this is not a full command list, if you’re missing commands, feel free to ask over at the Community.
ticket_hook setting¶
This will give you the ticket hook that you’ll find inside the []
in front
of the ticket number. By default this will be Ticket# - you shouldn’t change
this setting in a productive system.
>> Setting.get('ticket_hook')
FQDN setting¶
Get the current FQDN setting of Zammad and, if needed, adjust it.
Note
This setting has no effect on SSL certificates or any web server configurations.
>> Setting.get('fqdn') # Get current FQDN
>> Setting.set('fqdn', 'new.domain.tld') # Set a new FQDN
HTTP(s) setting¶
This setting indirectly belongs to your FQDN setting and is relevant for variable based URLs (e.g. in notifications) Zammad generated.
Warning
This setting also affects Zammad’s CSRF token behavior. If you set this setting to e.g. HTTPs but you’re using HTTP, logging in will fail!
Note
This setting has no effect on SSL certificates or any web server configurations.
>> Setting.get('http_type') # Get the current http type
>> Setting.set('http_type', 'https') # Change the http type to HTTPs
Storage provider setting¶
The storage provider setting is set to DB
on default installations.
However, if you receive a lot of attachments or have a fairly busy installation,
using the database to store attachments is not the best approach.
Use the following command
>> Setting.get('storage_provider') # get the current Attachment-Storage
>> Setting.set('storage_provider', 'DB') # Change Attachment-Storage to database
The following settings are available in a default installation:
DB
(database)
File
(Filesystem (/opt/zammad/storage/
))
Configuring Elasticsearch¶
If your Elasticsearch installation changes, you can use the following commands to ensure that Zammad still can access Elasticsearch.
>> Setting.set('es_url', 'http://127.0.0.1:9200') # Change elasticsearch URL to poll
>> Setting.set('es_user', 'elasticsearch') # Change elasticsearch user (e.g. for authentication)
>> Setting.set('es_password', 'zammad') # Change the elasticsearch password for authentication
>> Setting.set('es_index', Socket.gethostname + '_zammad') # Change the index name
>> Setting.set('es_attachment_ignore', %w[.png .jpg .jpeg .mpeg .mpg .mov .bin .exe .box .mbox]) # A list of ignored file extensions (they will not be indexed)
>> Setting.set('es_attachment_max_size_in_mb', 50) # Limit the Attachment-Size to push to your elasticsearch index
>> Setting.set('es_ssl_verify', 'false') # Turn SSL verification on or off
Enable proxy¶
Zammad needs to use a proxy for network communication? Set it here.
>> Setting.set('proxy', 'proxy.example.com:3128')
>> Setting.set('proxy_username', 'some user')
>> Setting.set('proxy_password', 'some pass')
Advanced customization settings¶
On this page you can find some settings that you won’t find within the Zammad UI. Those settings might come in handy as it can change Zammad’s behavior.
Note
Please note that this is not a full command list, if you’re missing commands, feel free to ask over at the Community.
Send all outgoing E-Mails to a BCC-Mailbox¶
This option allows you to send all outgoing E-Mails (not notifications) to a specific mailbox. Please note that this shouldn’t be a mailbox you’re importing already! This will apply to all groups and is a global setting.
>> Setting.set('system_bcc', 'alias@domain.tld')
You can easily check the current BCC-Setting by running the following:
>> Setting.get('system_bcc')
Activate counter on grouped overviews¶
This is a hidden setting which you can only set via Command-Line. This will globally enable a ticket number value in each heading for grouped elements.
>> Setting.set('ui_table_group_by_show_count', true) # enable counter on grouped overviews
>> Setting.set('ui_table_group_by_show_count', false) # disable counter on grouped overviews
>> Setting.get('ui_table_group_by_show_count') # get current setting (`nil` is false)

Default ticket type on creation¶
Zammad allows you to define the default article type upon ticket creation. By default this will be a incoming phone call.
You can choose between
phone-in
(incoming call, default),
phone-out
(outgoing call) and
email-out
(Sending an E-Mail out).
>> Setting.set('ui_ticket_create_default_type', 'email-out')
To check what setting is set currently, simply run:
>> Setting.get('ui_ticket_create_default_type')
Adding a warning to the ticket creation process¶
If in case you need to give your agent a note or warning during ticket creation, you can do so with the below command.
You can use three different warnings for
Incoming Calls
:"phone-in"=>""
,Outgoing Calls
:"phone-out"=>""
andOutgoing E-Mails
:"email-out"=>""
.
>> Setting.set('ui_ticket_create_notes', {
:"phone-in"=>"You're about to note a incoming phone call.",
:"phone-out"=>"You're about to note an outgoing phone call.",
:"email-out"=>"You're going to send out an E-Mail."
})
Note
You can use those three sub-settings independently, if you e.g. don’t need a
warning on incoming calls, simply leave out :"phone-in"=>""
out of the
setting. The setting itself is done within an array ( {}
).
To check what’s currently set, you can use:
>> Setting.get('ui_ticket_create_notes')
Sample of the above setting:

Adding a warning to the article reply process¶
In case you need to give your agent a warning during the ticket article reply, you can do that with the command below.
You can provide different warnings for different channels and article visibility
Internal Notes
:"note-internal"=>""
,Public Notes
:"note-public"=>""
,Internal Calls
:"phone-internal"=>""
,Public Calls
:"phone-public"=>""
,Internal Emails
:"email-internal"=>""
andPublic Emails
:"email-public"=>""
.
>> Setting.set('ui_ticket_add_article_hint', {
:"note-internal"=>"You are writing an |internal note|, only people of your organization will see it.",
:"note-public"=>"You are writing a |public note|.",
:"phone-internal" => "You are writing an |internal phone note|, only people of your organization will see it.",
:"phone-public"=>"You are writing a |public phone note|.",
:"email-internal" => "You are writing an |internal Email|, only people of your organization will see it.",
:"email-public"=>"You are writing a |public Email|."
})
Note
You can use example sub-settings above independently, if you e.g. don’t need
a warning on internal calls, simply leave out :"phone-internal"=>""
out
of the setting. The setting itself is in a form of an array ( {}
).
To check what’s currently set, you can use:
>> Setting.get('ui_ticket_add_article_hint')
Sample of the above setting:

Show Email address of customer on customer selection (ticket creation)¶
By default Zammad will not display the E-Mail-Addresses of customers. The below option allows you to change this behavior.
>> Setting.set('ui_user_organization_selector_with_email', true)
Get the current state of this setting with:
>> Setting.get('ui_user_organization_selector_with_email')
Change font settings for outgoing HTML mails¶
Note
Some clients (like Outlook) might fallback to other settings while it might work for other clients.
The below setting allows you to adjust Zammad’s email font setting. This setting does not require a service restart.
>> Setting.set("html_email_css_font", "font-family:'Helvetica Neue', Helvetica, Arial, Geneva, sans-serif; font-size: 12px;")
If you want to check the current setting, you can simply run the below code.
>> Setting.get('html_email_css_font')
Highlight customer’s open ticket count¶
This option enhances the selected customer’s open tickets count. It highlights the count in different colors if they hit a threshold.
>> Setting.set('ui_sidebar_open_ticket_indicator_colored', true)
Sample of the above setting:

Above settings has specific thresholds as follows. You cannot adjust these thresholds.
Situational threshold list for open ticket indication¶ Situation / View
no indication
warning (orange)
danger (red)
Ticket Zoom
< 2
2
>= 3
New Ticket dialog
0
1
>= 2
Working on user information¶
Note
Please note that this is not a full command list, if you’re missing commands, feel free to ask over at the Community.
Find user¶
In order to work on user information or to check for specific information, you’ll need to find it first.
>> User.find(4) # We already know the ID of the user
>> User.find_by(email: 'your@email') # Searching for the user by his Email address
>> User.find_by(login: 'john.doe') # Searching for the user by his login
Unlock a locked user account¶
Tip
Unlocking a locked user account is also supported by Zammad’s web UI. Please refer to the admin documentation for more information.
It sometimes happens that a user locks himself out by wildly trying the wrong password multiple times. Depending on your maximum failing login count (default: 10 times), Zammad might lock the account.
The user can’t login any more (forever) if he doesn’t change the password or you reset the counter.
>> u=User.find(**USERID**)
>> u.login_failed=0
>> u.save!
You can also double check if the account is locked by running the following (result needs to be 1 above your limit, so 11 for the default of 10 failing logins)
>> User.find(**USERID**).login_failed
Change / Update Email address of user¶
If needed, you can simply change the Email address of the user.
Note
Please note that the login attribute is not affected by this and Zammad thus might show different information within the UI.
>> u = User.find(**USERID**)
>> u.email = 'user@exmaple.com'
>> u.save!
You need to find the user ID of the user first for this.
Change / Update Login name of user¶
Change the user name of the user (e.g. if you want to login with a shorter username instead of a mail address)
>> u = User.find(**USERID**)
>> u.login = 'user@exmaple.com'
>> u.save!
You need to find the user ID of the user first for this.
Set admin rights for user¶
Don’t have access to Zammad anymore? Grant yourself or another user administrative rights.
>> u = User.find_by(email: 'you@example.com')
>> u.roles = Role.where(name: ['Agent', 'Admin'])
>> u.save!
Set password for user¶
You or the user did forget his password? No problem! Simply reset it by hand if needed.
>> User.find_by(email: 'you@example.com').update!(password: 'your_new_password')
Remove password for user¶
If you added a second authentication method (e.g. LDAP) after launch, there still may be a password in Zammad’s own user management. In cases like that users will be able to login with their (local) Zammad password in addition to the credentials stored on the external authentication provider. Simply remove the password stored by Zammad.
>> User.find_by(email: 'you@example.com').update!(password: nil)
Working with ticket information¶
Note
Please note that this is not a full command list, if you’re missing commands, feel free to ask over at the Community.
Get the RAW mail that Zammad fetched¶
The following command will help you to check on received EML-files Zammad fetched. This comes in handy if you delete Mails upon fetching and you need to check the EML-file itself.
To get the first articles EML-file, you can use the following command.
In our example the ticket number in question is 101234
.
>> Ticket.find_by(number:'101234').articles.first.as_raw.content
If needed, you can also get the raw content of later articles (you’ll need to
find the correct article though). Again, we expect 101234
to be our ticket
number. In the first step we get all article IDs of the ticket, from the list
we get, we can then get the articles content.
>> Ticket.find_by(number:'101234').article_ids
=> [4, 3, 2]
>> Ticket::Article.find(3).as_raw.content
Note
If you just use Ticket::Article.find(3)
you can see further information
(like who sent the mail, when we fetched it, …).
Update all tickets of a specific customer¶
Warning
Please note that this action can be expensive resource wise, if you have many tickets, this might slow down Zammad.
>> Ticket.where(customer_id: 4).update_all(customer_id: 1)
Priorities¶
Ticket priorities help your agent to see how important a customer request is. Priorities are not available to customers and, Core wise, have no impact on how Zammad handles a ticket. You can however adjust Zammad’s behavior with e.g. triggers, SLAs and schedulers.
Not sure what priorities are available in the system? Either have a look in any ticket or run the following command.
>> Ticket::Priority.pluck(:name)
Adding priorities for tickets¶
Ticket priorities come with several attributes, however, the most relevant
as of now are: name
, default_create
and ui_color
.
Warning
default_create
allows you to define the default priority Zammad should use during ticket creation. However - on default installations this is the priority2 normal
.You cannot have more than one priority as the default_create priority!
Note
ui_color
defines the CSS class to use. On default installations you can either uselow-priority
(light blue) orhigh-priority
(red). This affects how Zammad displays the ticket titles in overviews.
>> Ticket::Priority.create(
name: '4 super high',
default_create: false,
ui_color: 'high-priority',
created_by_id: 1,
updated_by_id: 1
)
Change priority¶
If needed you can also set priorities to inactive or rename them if they don’t fit your desired scheme. Renaming would look like so:
>> Ticket::Priority.find_by(name: '1 low').update(name: '1 high')
Get ticket state types¶
This will show all state types needed for creating new ticket states.
Tip
What are state types?
Zammad uses state types to know what it should do with your state. This allows you to have different types like pending actions, pending reminders or closed states.
State types also indicate the color scheme to be used. You can learn more about that in our user documentation.
If you want to add custom states, have a look in our admin documentation section.
>> Ticket::StateType.pluck(:id, :name)
Above will return both, the type ID and name - e.g.:
[[1, "new"], [2, "open"], ...
.
Add new ticket state¶
Note
🤓 Missing States you just created?
You might want to use
Ticket::State.pluck(:id, :name)
to get a listing of all available ticket states.Tip
🙈 ignoring escalations
You can use
ignore_escalation: true,
to ignore possible SLA calculations (pending reminder and pending close do this by default).
Non-Pending states¶
A state that’s not a pending state (e.g. open, closed). Just replace 'open'
by whatever you need (like closed).
>> Ticket::State.create_or_update(
name: 'Developing',
state_type: Ticket::StateType.find_by(name: 'open'),
created_by_id: 1,
updated_by_id: 1,
)
Pending reminders¶
A pending reminder state that will send a reminder notification to the agent if the time has been reached.
>> Ticket::State.create_or_update(
name: 'pending customer feedback',
state_type: Ticket::StateType.find_by(name: 'pending reminder'),
ignore_escalation: true,
created_by_id: 1,
updated_by_id: 1,
)
Pending Action¶
A pending action that will change to another state if “pending till” has been reached.
>> Ticket::State.create_or_update(
name: 'pending and reopen',
state_type: Ticket::StateType.find_by(name: 'pending action'),
ignore_escalation: true,
next_state: Ticket::State.find_by(name: 'open'),
created_by_id: 1,
updated_by_id: 1,
)
(optional) Disable date and time picker (pending till) for pending states¶
Starting with Zammad 5.0, Core Workflows automatically handles displaying the “pending till” field for pending states. Below snippet is not required and is only relevant if you don’t want to create a workflow within the UI of Zammad.
Replace pending customer feedback
with the pending state of your choice.
>> CoreWorkflow.create_if_not_exists(
name: 'remove pending till on state "pending customer feedback"',
object: 'Ticket',
condition_selected: { 'ticket.state_id'=>{ 'operator' => 'is', 'value' => Ticket::State.find_by(name: 'pending customer feedback').id.to_s } },
perform: { 'ticket.pending_time'=> { 'operator' => 'remove', 'remove' => 'true' } },
created_by_id: 1,
updated_by_id: 1,
)
Make new states available to UI¶
Before being able to use the new states within the WebApp, you need to run the following commands to make them available.
Warning
Please do not replace anything below, state_id is a named attribute which is correct and shall not be replaced!
>> attribute = ObjectManager::Attribute.get(
object: 'Ticket',
name: 'state_id',
)
>> attribute.data_option[:filter] = Ticket::State.by_category(:viewable).pluck(:id)
>> attribute.screens[:create_middle]['ticket.agent'][:filter] = Ticket::State.by_category(:viewable_agent_new).pluck(:id)
>> attribute.screens[:create_middle]['ticket.customer'][:filter] = Ticket::State.by_category(:viewable_customer_new).pluck(:id)
>> attribute.screens[:edit]['ticket.agent'][:filter] = Ticket::State.by_category(:viewable_agent_edit).pluck(:id)
>> attribute.screens[:edit]['ticket.customer'][:filter] = Ticket::State.by_category(:viewable_customer_edit).pluck(:id)
>> attribute.save!
Limit available states for customers¶
Tip
Core Workflows allows you to achieve below described behavior any time without any issues. No need to use the console if you don’t want to!
By default Zammad allows customers to change Ticket states to open
and
closed
. If this does not meet your requirenments, you can adjust this at
anytime. The below example shows how to restrict your customer to only close
tickets if needed:
>> attribute = ObjectManager::Attribute.get(
object: 'Ticket',
name: 'state_id',
)
>> attribute.screens['edit']['ticket.customer']['filter'] = Ticket::State.where(name: ['closed']).pluck(:id)
>> attribute.save!
Hint
If you want to allow several different states for customers, you need to
provide the state names as array - like so:
['closed', 'open', 'my-amazing-state']
(instead of ['closed']
).
You can check the current active states that customers can set like so:
>> ObjectManager::Attribute.get(
object: 'Ticket',
name: 'state_id',
).screens['edit']['ticket.customer']['filter']
The above will return one or more IDs - if you’re not sure which state they
belong to, you can check the state name with the following command.
(Ensure to replace {ID}
with your returned ID(s))
>> Ticket::State.find({ID}).name
Working with ticket articles¶
Note
Please note that this is not a full command list, if you’re missing commands, feel free to ask over at the Community.
Count Public “Notes” toward SLAs¶
Normally, notes don’t count toward service-level agreements. Use the following command to include publicly-visible notes when tracking SLA compliance. (Internal notes will never affect SLA calculations.)
Note
By default, customers are not notified when public notes are added to a ticket. Set up a trigger if you wish to change this behavior.
Warning
Changing this setting will disable the option to delete public notes.
>> Ticket::Article::Type.find_by(name:'note').update!(communication: true) # Enable SLA to count notes as communication
>> Ticket::Article::Type.find_by(name:'note').update!(communication: false) # Enable SLA to ignore notes as communication
Working with groups¶
Note
Please note that this is not a full command list, if you’re missing commands, feel free to ask over at the Community.
To open the rails console on the shell you have to enter the following commands.
Find group¶
>> Group.find_by(name: 'Users').follow_up_possible
Working with chat logs¶
Note
Please note that this is not a full command list, if you’re missing commands, feel free to ask over at the Community.
Removing IP address logs¶
Use the following command to remove all IP address records from closed chats that haven’t been updated in the last seven days:
>> Chat::Session.where(state: 'closed').where('updated_at < ?', 7.days.ago).each do |session|
next if session.preferences['remote_ip'].blank?
session.preferences.delete('geo_ip')
session.preferences.delete('remote_ip')
session.save!(touch: false)
end
Other useful commands¶
Note
Please note that this is not a full command list, if you’re missing commands, feel free to ask over at the Community.
Fetch mails¶
The below command will do a manual fetch of mail channels. This will also show errors that might appear within that process.
>> Channel.fetch
Reprocess unprocessable mails¶
When Zammad encounters a mail it cannot parse (e.g. due to a parser bug or a
malformed message), it will store the mail in
var/spool/unprocessable_mail/<ID>.eml
, give up on attempting to parse the
mail, and will warn on the monitoring page that there are unprocessed mails.
To force Zammad to reattempt to parse those mails, run the following command:
>> Channel::EmailParser.process_unprocessable_mails
In case of a malformed message (e.g. an invalid email address in one of the header fields), you may need to manually edit the mail before Zammad can process it.
If Zammad fails to process the message, it will remain in the
var/spool/unprocessable_mail
folder; otherwise it will be removed after it
has been parsed successfully.
Add translation¶
This comes in handy if you e.g. added a new state that you need to translate for several languages.
>> Translation.create_if_not_exists(:locale => 'de-de', :source => "New", :target => "Neu", created_by_id: 1, updated_by_id: 1)
Warning
While Zammad knows further attributes for the Translation model, please do not set them manually. Doing so may interfere with our Weblate translation process and cause you loosing your custom translations.
If you want to translate code base strings that are available within standard code, please use Weblate instead.
Translating attributes¶
By default Zammad will not translate custom attributes.
With the following code you can enable translation.
This will translate the attribute display name and the display names of values
(if it’s a value field). For this to work, just replace {attribute-name}
with the name of your attribute.
>> attribute = ObjectManager::Attribute.find_by(name: '{attribute-name}')
>> attribute.data_option[:translate] = true # set this to false to disable
# translation again
>> attribute.save!
Note
Translating value display names works for the following attribute types:
Boolean
Select
Tree Select
If you’re translating the display name of e.g. an Integer-attribute, this works as well!
Fill a test system with test data¶
Danger
Don’t run this in a productive environment! This can slow down Zammad and is hard to revert if you create much!
The below command will add 50
agents, 1000
customers, 20
groups,
40
organizations, 5
new overviews and 100
tickets.
You can always use 0
to not create specific items.
Zammad will create random data which make no logical sense.
>> FillDb.load(agents: 50,customers: 1000,groups: 20,organizations: 40,overviews: 5,tickets: 100,)
Deleting records¶
Danger
☠️ The commands listed here cause irrecoverable data loss! Only proceed if you know what you’re doing and you have a backup!
Note
Please note that this is not a full command list, if you’re missing commands, feel free to ask over at the Community.
Removing tickets (and their articles)¶
# Delete a ticket (specified by database ID)
>> Ticket.find(4).destroy
# Delete all tickets
>> Ticket.destroy_all
# Keep some tickets (specified by database ID); delete the rest
>> tickets_to_keep = [1, 2, 3]
>> Ticket.where.not(id: tickets_to_keep).destroy_all
Removing users¶
Warning
Customers may not be deleted while they have tickets remaining in the system.
As such, the examples below will delete not only the specified customers, but all tickets associated with them, as well. Below commands remove upon executing without any further warnings.
Hint
If you’re not sure what to do and need to learn more about what Zammad does upon removing users, please consider using Zammad’s UI options in stead.
Our documentation for the data privacy function will help you a lot!
Removing users is possible in 2 ways: A single user and in bulk.
>> User.find_by(email: '<email address>').destroy
>> User.where(
email: ['<email address 1>', '<email address 2>']
).destroy_all
Removing organizations¶
Note
Removing an organization does not delete associated customers.
- Step 1: Select organizations
# by "active" status >> organizations = Organization.where(active: false) # by name >> organizations = Organization.where(name: 'Acme') # by partial match on notes >> organizations = Organization.where('note LIKE ?', '%foo%')
- Step 2: Preview affected organizations
>> puts organizations.map { |org| "ORGANIZATION #{org.name}" }.join("\n")
- Step 3: Proceed with deletion
>> organizations.each do |org| puts %{Preparing deletion of organization "#{org.name}"...} org.members.each do |member| puts " Removing #{member.fullname} from organization..." member.update!(organization_id: nil) end puts " Deleting #{org.name}..." org.destroy end
Removing system records¶
# Remove all online notifications
>> OnlineNotification.destroy_all
# Remove all entries from the Activity Stream (dashboard)
>> ActivityStream.destroy_all
# Remove entries for all recently viewed objects
# (tickets, users, organizations)
>> RecentView.destroy_all
# Remove all history information from tickets, users and organizations
# (dangerous!)
>> History.destroy_all
Reset Zammad installation¶
Hint
Below commands are incomplete intentionally, error outputs will hint you through! The following operations will cause data loss and are for development / testing only.
Don’t forget to stop Zammad before trying to drop the database!
$ rake db:drop
$ rake db:create
$ rake db:migrate
$ rake db:seed
Start¶
We will be very happy if you decide to contribute to Zammad. You can do this in several ways. Contributions are mainly done by forking one of our repos on GitHub and creating a pull request with your changes (except for translations, see below). 🚀
You can contribute to:
Plese have a look on our notes on how to contribute below.
All repos can be found at https://github.com/zammad
Zammad Source Code¶
The Zammad source code can be found on GitHub at https://github.com/zammad/zammad
For more information on how to contribute to Zammad, please have a look at https://zammad.org/participate and at the Developer Manual.
Supported Branches / Versions¶
The main Zammad repository at https://github.com/zammad/zammad has several branches.
develop
¶
This is the current (unreleased) development state of next major release (this will become the new
stable
branch).Don’t use it for production!
Supported with bug and security fixes - see also our Security Policy.
stable
¶
This is the current stable release, e.g. Zammad 5.2.
Use this branch for production installations.
Supported with bug and security fixes - see also our Security Policy.
stable-x.y
¶
These are the branches of old versions of Zammad like
stable-5.1
for Zammad 5.1.No support for bug or security issues is provided.
Documentation¶
Do you want to contribute to the Zammad documentation?
Open a new GitHub pull request at
with your changes.
The Zammad documentation is hosted on Read the Docs. You can read it there at
https://docs.zammad.org (this documentation)
or browse the files via GitHub which also renders the used ReStructuredText markup.
ReStructuredText markup¶
If you like to edit the docs, use the ReStructuredText markup language. Information about this language can be found at:
Thanks! ❤ ❤ ❤
Zammad Team
Translation¶
If you want to help us with translation and improve the multi-language support of Zammad and/or the documentation, you are welcome to contribute as well! The translation of Zammad itself and the documentation is done by using Weblate, which is a service for the collaborative translation of projects.
You just have to head over to Zammad’s Weblate instance. You can either create an account (if you don’t have one already) or even sign in with your Github account!
We will cover some basic steps in the following sections to get you started with translating. However, if you want to use some additional features of Weblate and want to dive deeper into it, their translation documentation is a good starting point.
Basics¶
The translation of Zammad and the translation of the documentation are split into two projects in Weblate. When you click in the top menu under “Projects > Browse all projects”, you can find the overview of the two projects:

Screenshot showing translation projects and menu bar of Weblate¶
Structure of translation projects in Weblate:
Documentation
User Documentation (latest)
User Documentation (pre-release)
Admin Documentation (latest)
Admin Documentation (pre-release)
Zammad
Zammad (development version)
Zammad (stable version)
Some more which aren’t relevant here
Note
It is no big difference if you choose latest
or pre-release
(for the
documentation) or development version
or stable version
(for
Zammad). When Weblate detects the same strings in different versions,
they will be used for all versions and only have to be translated once.
After selecting a project (Documentation or Zammad), you will see different sub-projects and their translation status summarized for all languages. These overviews may show a quite low translation rate, which is due to the amount of acive languages.
Here you can select one of the “components”, which is more or less the same as different versions. After selecting one of them, you can see the status of translation for the different languages, as you can see in the following screenshot with an example from Documentation > User Documentation (latest):

Screenshot showing translation status of different languages for the user documentation¶
Translating¶
After selecting your languange you want to translate to, a good starting point is to select “Untranslated strings” (or the same meaning in your language, depending on what you have set in your profile).
After that, you will finally see the first untranslated string in the upper field and, in theory, you can start to translate. First a brief overview of the user interface of Weblate:
Breadcrumbs with path to the current project and language
Translation area itself. You can find the source string (“English (United States)”) at the top and the field for your translation (“French” in this example).
Glossary: here you can find common translations in Zammad context. The terms from the glossary are highlighted in the source strings, as well.
Some useful tabs:
Nearby strings: shows you the context of the word or string
Automatic suggestions: here you can find automatic suggestions from DeepL and suggestions from similar strings, which are already translated. Use the “Clone to translation” button to insert it in the translation field to apply changes. Use the “Accept” button to accept the suggested translation and automatically switch to the next string.
Other languages: here you can get an overview, which languages are translated and you can also see the translated strings (could be useful for languages, which are similar).
Troubleshooting¶
And finally some notes for “special” source strings, you might see in the documentation projects (see RestructuredText for details):
- ``example-string``
This is rendered as
example-string
. Depending on the context, it can be translated or not. In any case, use the `` before and after the string in your translation.- :doc:`example <path/to/document>`
This is a link to another page. Some links doesn’t have the “example” part included, e.g. :doc:`path/to/document. The above “example” is the text, which is shown as link. This part can be translated. The path/to/document may not be translated, otherwise the link would not work anymore.
- `some text <https://example.com>`_
This is a link which can refer to an external website. “some text” is the displayed text in the documentation, the part between < and > is the link target. The _ at the end is important and must remain in the translated text.
- :admin-docs:`some text </manage-text-modules.html>`
This is a link which refers to external documentation. “some text” is the displayed text in the documentation, the part between < and > is the link target. Note the absence of _ at the end, since this link is using a different construction mechanism.
- **example string**
Markup for text (e.g. bold, italics). Alternative: *example string*. These strings can be translated, but the markup labeling (e.g. one or more *) should be adopted true to meaning.
Introduction¶
Zammad provides a powerful REST-API which allows all operations that are available via UI as well.
This page gives you a first impression for things that generally count for all endpoints and how to adapt.
API clients¶
There are API clients available. Please note that these clients may not provide access to all available endpoints listed here.
Ruby Client (Official)
PHP Client (Official)
Python Client (Third-Party)
.NET Client (Third-Party)
Android API-Client (Third-Party)
Go Client (Third-Party; API client only, no “ready to use” App)
Authentication¶
Zammad supports three different authentication methods for its API.
- HTTP Basic Authentication (username/password)
- The username / password must be provided as HTTP header in the HTTP call.This authentication method can be disabled and may not be available in your system.
$ curl -u {username}:{password} https://{fqdn}/{endpoint}
Note
We strongly suggest against using basic authentication. Use access tokens when ever possible!
- HTTP Token Authentication (access token)
- The access token must be provided as HTTP header in the HTTP call.Each user can create several access tokens in their user preferences.This authentication method can be disabled and may not be available in your system.
$ curl -H "Authorization: Token token={your_token}" https://{fqdn}/{endpoint}
- OAuth2 (token access)
- The token must be provided as HTTP header in your calls.This allows 3rd party applications to authenticate against Zammad.
$ curl -H "Authorization: Bearer {your_token}" https://{fqdn}/{endpoint}
Endpoints and example data¶
For simplicity we’ll not provide specific commands on the next pages, but
instead tell the possible call method (e.g. GET
) and the endpoint to use
(e.g. /api/v1/users
). In case Zammad expects information within these
endpoint urls, we’ll put them into curly braces like so:
/api/v1/users/{user id}
The response format will be a complete JSON response from a default Zammad instance. Please keep in mind that you may see more fields or general information in case you added objects or other information.
Content Type¶
Zammad returns JSON payloads whenever you retrieve data.
If you’re going to provide data, no matter of the general request type,
don’t forget to provide the content type application/json
as well.
Response Payloads (expand)¶
Zammad always returns information including hints to all relations.
If you need more information than that (because IDs may not be enough) you
can also extend your endpoint calls with ?expand=true
.
This switch will provide even more information — at least named relations on top of the ID ones. Below you can find two examples to compare - one for ticket and user each.
{
"active": true,
"login_failed": 0,
"verified": false,
"source": null,
"login": "chris@chrispresso.com",
"last_login": "2021-09-23T13:17:24.817Z",
"id": 3,
"updated_by_id": 1,
"organization_id": 2,
"firstname": "Christopher",
"lastname": "Miller",
"email": "chris@chrispresso.com",
"image": "7a6a0d1d94ad2037153cf3a6c1b49a53",
"image_source": null,
"web": "",
"phone": "",
"fax": "",
"mobile": "",
"department": "",
"street": "",
"zip": "",
"city": "",
"country": "",
"address": "",
"vip": false,
"note": "",
"out_of_office": false,
"out_of_office_start_at": null,
"out_of_office_end_at": null,
"out_of_office_replacement_id": null,
"preferences":
{
"notification_config":
{
"matrix":
{
"create":
{
"criteria":
{
"owned_by_me": true,
"owned_by_nobody": true,
"subscribed": true,
"no": true
},
"channel":
{
"email": true,
"online": true
}
},
"update":
{
"criteria":
{
"owned_by_me": true,
"owned_by_nobody": true,
"subscribed": true,
"no": true
},
"channel":
{
"email": true,
"online": true
}
},
"reminder_reached":
{
"criteria":
{
"owned_by_me": true,
"owned_by_nobody": false,
"no": true
},
"channel":
{
"email": true,
"online": true
}
},
"escalation":
{
"criteria":
{
"owned_by_me": true,
"owned_by_nobody": false,
"no": true
},
"channel":
{
"email": true,
"online": true
}
}
},
"group_ids":
[
"2",
"1",
"3"
]
},
"locale": "de-de",
"intro": true,
"notification_sound":
{
"file": "Xylo.mp3",
"enabled": true
},
"cti": true,
"tickets_closed": 0,
"tickets_open": 1
},
"created_by_id": 1,
"created_at": "2021-07-26T14:44:41.066Z",
"updated_at": "2021-09-23T13:17:24.825Z",
"role_ids":
[
1,
2
],
"organization_ids":
[],
"authorization_ids":
[],
"karma_user_ids":
[
1
],
"group_ids":
{
"1":
[
"full"
],
"2":
[
"full"
],
"3":
[
"full"
]
}
}
{
"active": true,
"login_failed": 0,
"verified": false,
"source": null,
"login": "chris@chrispresso.com",
"last_login": "2021-09-23T13:17:24.817Z",
"id": 3,
"updated_by_id": 1,
"organization_id": 2,
"firstname": "Christopher",
"lastname": "Miller",
"email": "chris@chrispresso.com",
"image": "7a6a0d1d94ad2037153cf3a6c1b49a53",
"image_source": null,
"web": "",
"phone": "",
"fax": "",
"mobile": "",
"department": "",
"street": "",
"zip": "",
"city": "",
"country": "",
"address": "",
"vip": false,
"note": "",
"out_of_office": false,
"out_of_office_start_at": null,
"out_of_office_end_at": null,
"out_of_office_replacement_id": null,
"preferences":
{
"notification_config":
{
"matrix":
{
"create":
{
"criteria":
{
"owned_by_me": true,
"owned_by_nobody": true,
"subscribed": true,
"no": true
},
"channel":
{
"email": true,
"online": true
}
},
"update":
{
"criteria":
{
"owned_by_me": true,
"owned_by_nobody": true,
"subscribed": true,
"no": true
},
"channel":
{
"email": true,
"online": true
}
},
"reminder_reached":
{
"criteria":
{
"owned_by_me": true,
"owned_by_nobody": false,
"no": true
},
"channel":
{
"email": true,
"online": true
}
},
"escalation":
{
"criteria":
{
"owned_by_me": true,
"owned_by_nobody": false,
"no": true
},
"channel":
{
"email": true,
"online": true
}
}
},
"group_ids":
[
"2",
"1",
"3"
]
},
"locale": "de-de",
"intro": true,
"notification_sound":
{
"file": "Xylo.mp3",
"enabled": true
},
"cti": true,
"tickets_closed": 0,
"tickets_open": 1
},
"created_by_id": 1,
"created_at": "2021-07-26T14:44:41.066Z",
"updated_at": "2021-09-23T13:17:24.825Z",
"role_ids":
[
1,
2
],
"organization_ids":
[],
"authorization_ids":
[],
"karma_user_ids":
[
1
],
"group_ids":
{
"1":
[
"full"
],
"2":
[
"full"
],
"3":
[
"full"
]
},
"roles":
[
"Admin",
"Agent"
],
"organizations":
[],
"authorizations":
[],
"organization": "Chrispresso Inc.",
"groups":
{
"Sales":
[
"full"
],
"2nd Level":
[
"full"
],
"Service/Desk":
[
"full"
]
},
"created_by": "-",
"updated_by": "-"
}
{
"id": 3,
"group_id": 1,
"priority_id": 2,
"state_id": 4,
"organization_id": 3,
"number": "71003",
"title": "Order 787556",
"owner_id": 3,
"customer_id": 7,
"note": null,
"first_response_at": null,
"first_response_escalation_at": null,
"first_response_in_min": null,
"first_response_diff_in_min": null,
"close_at": null,
"close_escalation_at": null,
"close_in_min": null,
"close_diff_in_min": null,
"update_escalation_at": null,
"update_in_min": null,
"update_diff_in_min": null,
"last_contact_at": "2021-02-26T12:44:43.888Z",
"last_contact_agent_at": "2021-02-26T12:44:43.888Z",
"last_contact_customer_at": "2021-02-24T14:44:43.828Z",
"last_owner_update_at": null,
"create_article_type_id": 1,
"create_article_sender_id": 2,
"article_count": 2,
"escalation_at": null,
"pending_time": null,
"type": null,
"time_unit": null,
"preferences":
{},
"updated_by_id": 4,
"created_by_id": 7,
"created_at": "2021-02-24T14:44:43.828Z",
"updated_at": "2021-07-26T14:44:43.906Z"
}
{
"id": 3,
"group_id": 1,
"priority_id": 2,
"state_id": 4,
"organization_id": 3,
"number": "71003",
"title": "Order 787556",
"owner_id": 3,
"customer_id": 7,
"note": null,
"first_response_at": null,
"first_response_escalation_at": null,
"first_response_in_min": null,
"first_response_diff_in_min": null,
"close_at": null,
"close_escalation_at": null,
"close_in_min": null,
"close_diff_in_min": null,
"update_escalation_at": null,
"update_in_min": null,
"update_diff_in_min": null,
"last_contact_at": "2021-02-26T12:44:43.888Z",
"last_contact_agent_at": "2021-02-26T12:44:43.888Z",
"last_contact_customer_at": "2021-02-24T14:44:43.828Z",
"last_owner_update_at": null,
"create_article_type_id": 1,
"create_article_sender_id": 2,
"article_count": 2,
"escalation_at": null,
"pending_time": null,
"type": null,
"time_unit": null,
"preferences":
{},
"updated_by_id": 4,
"created_by_id": 7,
"created_at": "2021-02-24T14:44:43.828Z",
"updated_at": "2021-07-26T14:44:43.906Z",
"article_ids":
[
5,
6
],
"ticket_time_accounting_ids":
[],
"group": "Sales",
"organization": "Awesome Customer Inc.",
"ticket_time_accounting":
[],
"state": "closed",
"priority": "2 normal",
"owner": "chris@chrispresso.com",
"customer": "samuel@example.com",
"created_by": "samuel@example.com",
"updated_by": "jacob@chrispresso.com",
"create_article_type": "email",
"create_article_sender": "Customer"
}
Warning
Please note that Core Workflows may restrict access to attributes or values. See Core Workflows limitations to learn more.
Pagination¶
As Zammad limits the number of returned objects for performance reasons, you may have to use pagination at some points.
Note
Number of returned objects: Zammad has hard limits for the maximum returned objects. You can’t raise these limits.
Number of total to return objects: Zammad does not provide a total count of objects available for your query. This forces you to cycle through the pages until Zammad no longer returns further objects.
In order to use pagination you’ll need two get options:
per_page
and page
. Combine them like so to receive 5 results from
the first result page: ?page=1&per_page=5
- count page up to get
more results.
Sorting search results¶
Zammad allows you to sort your search results by field if needed.
- sort_by
Append
?sort_by={row name}
to your query to sort by a specific row that appears in the search result.- order_by
Append
?order_by={direction}
to your query to switch in between ascending and descending order.Directions are:
asc
anddesc
.
Note
Usually you’ll want to combine both parameters in your searches - e.g.:
?query={search string}&sort_by={row name}&order_by={direction}
Actions on behalf of other users¶
Requirement: the user used for running the query on behalf requires
admin.user
permission.
Running API queries on behalf of other users allows you to e.g. create tickets by a different user.
To do so, add a new HTTP header named From
to your request.
The value of this header can be one of the following:
user ID
user login
user email
From
is available for all endpoints.
Encoding¶
The API expects UTF-8 encoding.
Keep in mind that especially when using URLs with get options
(e.g. ?query=this
) you may need to encode your URL accordingly.
If you want to learn more about URL encoding, this Wikipedia article may be of help
User¶
Note
🤓 To see or not to see
Please note that below samples were provided with admin
and
ticket.agent
permissions. Some attributes / information may not be
available in specific situations.
Please see our Permission Guide to get better insights.
me - current user¶
Required permission: any
GET
-Request sent: /api/v1/users/me
Response:
# HTTP-Code 200 Ok
{
"id": 3,
"organization_id": 2,
"login": "chris@chrispresso.com",
"firstname": "Christopher",
"lastname": "Miller",
"email": "chris@chrispresso.com",
"image": "7a6a0d1d94ad2037153cf3a6c1b49a53",
"image_source": null,
"web": "",
"phone": "",
"fax": "",
"mobile": "",
"department": null,
"street": "",
"zip": "",
"city": "",
"country": "",
"address": null,
"vip": false,
"verified": false,
"active": true,
"note": "",
"last_login": "2021-11-03T12:26:53.410Z",
"source": null,
"login_failed": 0,
"out_of_office": false,
"out_of_office_start_at": null,
"out_of_office_end_at": null,
"out_of_office_replacement_id": null,
"preferences":
{
"notification_config":
{
"matrix":
{
"create":
{
"criteria":
{
"owned_by_me": true,
"owned_by_nobody": true,
"subscribed": true,
"no": false
},
"channel":
{
"email": true,
"online": true
}
},
"update":
{
"criteria":
{
"owned_by_me": true,
"owned_by_nobody": true,
"subscribed": true,
"no": false
},
"channel":
{
"email": true,
"online": true
}
},
"reminder_reached":
{
"criteria":
{
"owned_by_me": true,
"owned_by_nobody": false,
"subscribed": false,
"no": false
},
"channel":
{
"email": true,
"online": true
}
},
"escalation":
{
"criteria":
{
"owned_by_me": true,
"owned_by_nobody": false,
"subscribed": false,
"no": false
},
"channel":
{
"email": true,
"online": true
}
}
}
},
"locale": "en-us",
"intro": true
},
"updated_by_id": 3,
"created_by_id": 1,
"created_at": "2021-11-03T11:57:15.975Z",
"updated_at": "2021-11-03T12:26:55.642Z",
"role_ids":
[
1,
2
],
"organization_ids":
[],
"authorization_ids":
[],
"karma_user_ids":
[],
"group_ids":
{
"1":
[
"full"
],
"2":
[
"full"
],
"3":
[
"full"
]
}
}
List¶
Required permission: ticket.agent
or admin.user
Note
Technically, any listings will return users own information only.
GET
-Request sent: /api/v1/users
Response:
# HTTP-Code 200 Ok
[
{
"id": 1,
"organization_id": null,
"login": "-",
"firstname": "-",
"lastname": "",
"email": "",
"image": null,
"image_source": null,
"web": "",
"phone": "",
"fax": "",
"mobile": "",
"department": "",
"street": "",
"zip": "",
"city": "",
"country": "",
"address": "",
"vip": false,
"verified": false,
"active": false,
"note": "",
"last_login": null,
"source": null,
"login_failed": 0,
"out_of_office": false,
"out_of_office_start_at": null,
"out_of_office_end_at": null,
"out_of_office_replacement_id": null,
"preferences":
{},
"updated_by_id": 1,
"created_by_id": 1,
"created_at": "2021-11-03T11:51:12.786Z",
"updated_at": "2021-11-03T11:51:12.786Z",
"role_ids":
[],
"organization_ids":
[],
"authorization_ids":
[],
"karma_user_ids":
[],
"group_ids":
{}
},
{
"id": 2,
"organization_id": 1,
"login": "nicole.braun@zammad.org",
"firstname": "Nicole",
"lastname": "Braun",
"email": "nicole.braun@zammad.org",
"image": null,
"image_source": null,
"web": "",
"phone": "",
"fax": "",
"mobile": "",
"department": "",
"street": "",
"zip": "",
"city": "",
"country": "",
"address": "",
"vip": false,
"verified": false,
"active": true,
"note": "",
"last_login": null,
"source": null,
"login_failed": 0,
"out_of_office": false,
"out_of_office_start_at": null,
"out_of_office_end_at": null,
"out_of_office_replacement_id": null,
"preferences":
{
"tickets_closed": 0,
"tickets_open": 1
},
"updated_by_id": 2,
"created_by_id": 1,
"created_at": "2021-11-03T11:51:13.703Z",
"updated_at": "2021-11-03T12:01:05.411Z",
"role_ids":
[
3
],
"organization_ids":
[],
"authorization_ids":
[],
"karma_user_ids":
[],
"group_ids":
{}
},
{
"id": 3,
"organization_id": 2,
"login": "chris@chrispresso.com",
"firstname": "Christopher",
"lastname": "Miller",
"email": "chris@chrispresso.com",
"image": "7a6a0d1d94ad2037153cf3a6c1b49a53",
"image_source": null,
"web": "",
"phone": "",
"fax": "",
"mobile": "",
"department": null,
"street": "",
"zip": "",
"city": "",
"country": "",
"address": null,
"vip": false,
"verified": false,
"active": true,
"note": "",
"last_login": "2021-11-03T12:26:53.410Z",
"source": null,
"login_failed": 0,
"out_of_office": false,
"out_of_office_start_at": null,
"out_of_office_end_at": null,
"out_of_office_replacement_id": null,
"preferences":
{
"notification_config":
{
"matrix":
{
"create":
{
"criteria":
{
"owned_by_me": true,
"owned_by_nobody": true,
"subscribed": true,
"no": false
},
"channel":
{
"email": true,
"online": true
}
},
"update":
{
"criteria":
{
"owned_by_me": true,
"owned_by_nobody": true,
"subscribed": true,
"no": false
},
"channel":
{
"email": true,
"online": true
}
},
"reminder_reached":
{
"criteria":
{
"owned_by_me": true,
"owned_by_nobody": false,
"subscribed": false,
"no": false
},
"channel":
{
"email": true,
"online": true
}
},
"escalation":
{
"criteria":
{
"owned_by_me": true,
"owned_by_nobody": false,
"subscribed": false,
"no": false
},
"channel":
{
"email": true,
"online": true
}
}
}
},
"locale": "en-us",
"intro": true
},
"updated_by_id": 3,
"created_by_id": 1,
"created_at": "2021-11-03T11:57:15.975Z",
"updated_at": "2021-11-03T12:26:55.642Z",
"role_ids":
[
1,
2
],
"organization_ids":
[],
"authorization_ids":
[],
"karma_user_ids":
[],
"group_ids":
{
"1":
[
"full"
],
"2":
[
"full"
],
"3":
[
"full"
]
}
},
{
"id": 4,
"organization_id": 2,
"login": "jacob@chrispresso.com",
"firstname": "Jacob",
"lastname": "Smith",
"email": "jacob@chrispresso.com",
"image": "95afc1244af5cb8b77edcd7224c5d5f8",
"image_source": null,
"web": "",
"phone": "",
"fax": "",
"mobile": "",
"department": null,
"street": "",
"zip": "",
"city": "",
"country": "",
"address": null,
"vip": false,
"verified": false,
"active": true,
"note": "",
"last_login": null,
"source": null,
"login_failed": 0,
"out_of_office": false,
"out_of_office_start_at": null,
"out_of_office_end_at": null,
"out_of_office_replacement_id": null,
"preferences":
{
"notification_config":
{
"matrix":
{
"create":
{
"criteria":
{
"owned_by_me": true,
"owned_by_nobody": true,
"subscribed": true,
"no": false
},
"channel":
{
"email": true,
"online": true
}
},
"update":
{
"criteria":
{
"owned_by_me": true,
"owned_by_nobody": true,
"subscribed": true,
"no": false
},
"channel":
{
"email": true,
"online": true
}
},
"reminder_reached":
{
"criteria":
{
"owned_by_me": true,
"owned_by_nobody": false,
"subscribed": false,
"no": false
},
"channel":
{
"email": true,
"online": true
}
},
"escalation":
{
"criteria":
{
"owned_by_me": true,
"owned_by_nobody": false,
"subscribed": false,
"no": false
},
"channel":
{
"email": true,
"online": true
}
}
}
},
"locale": "en-us"
},
"updated_by_id": 1,
"created_by_id": 1,
"created_at": "2021-11-03T11:57:16.160Z",
"updated_at": "2021-11-03T11:57:16.214Z",
"role_ids":
[
1,
2
],
"organization_ids":
[],
"authorization_ids":
[],
"karma_user_ids":
[],
"group_ids":
{
"1":
[
"full"
],
"2":
[
"full"
],
"3":
[
"full"
]
}
},
{
"id": 5,
"organization_id": 2,
"login": "emma@chrispresso.com",
"firstname": "Emma",
"lastname": "Taylor",
"email": "emma@chrispresso.com",
"image": "b64fef91c29105b4a08a2a69be08eda3",
"image_source": null,
"web": "",
"phone": "",
"fax": "",
"mobile": "",
"department": null,
"street": "",
"zip": "",
"city": "",
"country": "",
"address": null,
"vip": false,
"verified": false,
"active": true,
"note": "",
"last_login": null,
"source": null,
"login_failed": 0,
"out_of_office": false,
"out_of_office_start_at": null,
"out_of_office_end_at": null,
"out_of_office_replacement_id": null,
"preferences":
{
"notification_config":
{
"matrix":
{
"create":
{
"criteria":
{
"owned_by_me": true,
"owned_by_nobody": true,
"subscribed": true,
"no": false
},
"channel":
{
"email": true,
"online": true
}
},
"update":
{
"criteria":
{
"owned_by_me": true,
"owned_by_nobody": true,
"subscribed": true,
"no": false
},
"channel":
{
"email": true,
"online": true
}
},
"reminder_reached":
{
"criteria":
{
"owned_by_me": true,
"owned_by_nobody": false,
"subscribed": false,
"no": false
},
"channel":
{
"email": true,
"online": true
}
},
"escalation":
{
"criteria":
{
"owned_by_me": true,
"owned_by_nobody": false,
"subscribed": false,
"no": false
},
"channel":
{
"email": true,
"online": true
}
}
}
},
"locale": "en-us"
},
"updated_by_id": 1,
"created_by_id": 1,
"created_at": "2021-11-03T11:57:16.349Z",
"updated_at": "2021-11-03T11:57:16.409Z",
"role_ids":
[
2
],
"organization_ids":
[],
"authorization_ids":
[],
"karma_user_ids":
[],
"group_ids":
{
"1":
[
"full"
],
"2":
[
"full"
],
"3":
[
"full"
]
}
},
{
"id": 6,
"organization_id": 3,
"login": "anna@example.com",
"firstname": "Anna",
"lastname": "Lopez",
"email": "anna@example.com",
"image": "4b1cb1fae2e608ffa72099774e1f57ad",
"image_source": null,
"web": "",
"phone": "415-123-5858",
"fax": "",
"mobile": "",
"department": null,
"street": "",
"zip": "",
"city": "",
"country": "",
"address": "Golden Gate Bridge\nSan Francisco, CA 94129",
"vip": false,
"verified": false,
"active": true,
"note": "likes espresso romano - recommended espresso con panna",
"last_login": null,
"source": null,
"login_failed": 0,
"out_of_office": false,
"out_of_office_start_at": null,
"out_of_office_end_at": null,
"out_of_office_replacement_id": null,
"preferences":
{},
"updated_by_id": 1,
"created_by_id": 1,
"created_at": "2021-11-03T11:57:16.526Z",
"updated_at": "2021-11-03T11:57:16.611Z",
"role_ids":
[
3
],
"organization_ids":
[],
"authorization_ids":
[],
"karma_user_ids":
[],
"group_ids":
{}
},
{
"id": 7,
"organization_id": 3,
"login": "samuel@example.com",
"firstname": "Samuel",
"lastname": "Lee",
"email": "samuel@example.com",
"image": "5911d228f3588c36a72d80eb0c1e4d08",
"image_source": null,
"web": "",
"phone": "855-666-7777",
"fax": "",
"mobile": "",
"department": null,
"street": "",
"zip": "",
"city": "",
"country": "",
"address": "5201 Blue Lagoon Drive\n8th Floor & 9th Floor\nMiami, FL 33126",
"vip": false,
"verified": false,
"active": true,
"note": "likes americano, did order two units",
"last_login": null,
"source": null,
"login_failed": 0,
"out_of_office": false,
"out_of_office_start_at": null,
"out_of_office_end_at": null,
"out_of_office_replacement_id": null,
"preferences":
{},
"updated_by_id": 1,
"created_by_id": 1,
"created_at": "2021-11-03T11:57:16.748Z",
"updated_at": "2021-11-03T11:57:16.861Z",
"role_ids":
[
3
],
"organization_ids":
[],
"authorization_ids":
[],
"karma_user_ids":
[],
"group_ids":
{}
},
{
"id": 8,
"organization_id": 3,
"login": "emily@example.com",
"firstname": "Emily",
"lastname": "Adams",
"email": "emily@example.com",
"image": "99ba64a89f7783c099c304c9b00ff9e8",
"image_source": null,
"web": "",
"phone": "0061 2 1234 7777",
"fax": "",
"mobile": "",
"department": null,
"street": "",
"zip": "",
"city": "",
"country": "",
"address": "Bennelong Point\nSydney NSW 2000",
"vip": false,
"verified": false,
"active": true,
"note": "did order café au lait, ask next time if the flavor was as expected",
"last_login": null,
"source": null,
"login_failed": 0,
"out_of_office": false,
"out_of_office_start_at": null,
"out_of_office_end_at": null,
"out_of_office_replacement_id": null,
"preferences":
{},
"updated_by_id": 1,
"created_by_id": 1,
"created_at": "2021-11-03T11:57:17.000Z",
"updated_at": "2021-11-03T11:57:17.060Z",
"role_ids":
[
3
],
"organization_ids":
[],
"authorization_ids":
[],
"karma_user_ids":
[],
"group_ids":
{}
},
{
"id": 9,
"organization_id": 4,
"login": "ryan@example.com",
"firstname": "Ryan",
"lastname": "Parker",
"email": "ryan@example.com",
"image": "0e405c60b5deb780feb7ebebd37ff5e0",
"image_source": null,
"web": "",
"phone": "0049 30 1234 5678",
"fax": "",
"mobile": "",
"department": null,
"street": "",
"zip": "",
"city": "",
"country": "",
"address": "Brandenburger Tor 7\n10117 Berlin",
"vip": false,
"verified": false,
"active": true,
"note": "no latte but macchiato",
"last_login": null,
"source": null,
"login_failed": 0,
"out_of_office": false,
"out_of_office_start_at": null,
"out_of_office_end_at": null,
"out_of_office_replacement_id": null,
"preferences":
{},
"updated_by_id": 1,
"created_by_id": 1,
"created_at": "2021-11-03T11:57:17.190Z",
"updated_at": "2021-11-03T11:57:17.250Z",
"role_ids":
[
3
],
"organization_ids":
[],
"authorization_ids":
[],
"karma_user_ids":
[],
"group_ids":
{}
},
{
"id": 10,
"organization_id": null,
"login": "david@example.com",
"firstname": "David",
"lastname": "Bell",
"email": "david@example.com",
"image": "d829d234f377f231534802df6d5500a7",
"image_source": null,
"web": "",
"phone": "0033 892 12 34 56",
"fax": "",
"mobile": "",
"department": null,
"street": "",
"zip": "",
"city": "",
"country": "",
"address": "Eiffel Tower\n5 Avenue Anatole France\n75007 Paris",
"vip": false,
"verified": false,
"active": true,
"note": "did order viennese melange, ask next time if the flavor was as expected",
"last_login": null,
"source": null,
"login_failed": 0,
"out_of_office": false,
"out_of_office_start_at": null,
"out_of_office_end_at": null,
"out_of_office_replacement_id": null,
"preferences":
{},
"updated_by_id": 1,
"created_by_id": 1,
"created_at": "2021-11-03T11:57:17.495Z",
"updated_at": "2021-11-03T11:57:17.561Z",
"role_ids":
[
3
],
"organization_ids":
[],
"authorization_ids":
[],
"karma_user_ids":
[],
"group_ids":
{}
},
{
"id": 11,
"organization_id": null,
"login": "olivia@example.com",
"firstname": "Olivia",
"lastname": "Ross",
"email": "olivia@example.com",
"image": "b6f7a2d56544bb471eb3a3c238c7d964",
"image_source": null,
"web": "",
"phone": "0044 20 1234 5678",
"fax": "",
"mobile": "",
"department": null,
"street": "",
"zip": "",
"city": "",
"country": "",
"address": "Westminster\nLondon SW1A 0AA",
"vip": false,
"verified": false,
"active": true,
"note": "",
"last_login": null,
"source": null,
"login_failed": 0,
"out_of_office": false,
"out_of_office_start_at": null,
"out_of_office_end_at": null,
"out_of_office_replacement_id": null,
"preferences":
{},
"updated_by_id": 1,
"created_by_id": 1,
"created_at": "2021-11-03T11:57:17.741Z",
"updated_at": "2021-11-03T11:57:17.794Z",
"role_ids":
[
3
],
"organization_ids":
[],
"authorization_ids":
[],
"karma_user_ids":
[],
"group_ids":
{}
}
]
Search¶
Required permission: ticket.agent
or admin.user
GET
-Request sent:
/api/v1/users/search?query=organization.name:{search string}&limit=10
Response:
# HTTP-Code 200 Ok
[
{
"id": 8,
"organization_id": 3,
"login": "emily@example.com",
"firstname": "Emily",
"lastname": "Adams",
"email": "emily@example.com",
"image": "99ba64a89f7783c099c304c9b00ff9e8",
"image_source": null,
"web": "",
"phone": "0061 2 1234 7777",
"fax": "",
"mobile": "",
"department": null,
"street": "",
"zip": "",
"city": "",
"country": "",
"address": "Bennelong Point\nSydney NSW 2000",
"vip": false,
"verified": false,
"active": true,
"note": "did order café au lait, ask next time if the flavor was as expected",
"last_login": null,
"source": null,
"login_failed": 0,
"out_of_office": false,
"out_of_office_start_at": null,
"out_of_office_end_at": null,
"out_of_office_replacement_id": null,
"preferences": {},
"updated_by_id": 1,
"created_by_id": 1,
"created_at": "2021-11-03T11:57:17.000Z",
"updated_at": "2021-11-03T11:57:17.060Z",
"role_ids": [
3
],
"organization_ids": [],
"authorization_ids": [],
"karma_user_ids": [],
"group_ids": {}
},
{
"id": 7,
"organization_id": 3,
"login": "samuel@example.com",
"firstname": "Samuel",
"lastname": "Lee",
"email": "samuel@example.com",
"image": "5911d228f3588c36a72d80eb0c1e4d08",
"image_source": null,
"web": "",
"phone": "855-666-7777",
"fax": "",
"mobile": "",
"department": null,
"street": "",
"zip": "",
"city": "",
"country": "",
"address": "5201 Blue Lagoon Drive\n8th Floor & 9th Floor\nMiami, FL 33126",
"vip": false,
"verified": false,
"active": true,
"note": "likes americano, did order two units",
"last_login": null,
"source": null,
"login_failed": 0,
"out_of_office": false,
"out_of_office_start_at": null,
"out_of_office_end_at": null,
"out_of_office_replacement_id": null,
"preferences": {},
"updated_by_id": 1,
"created_by_id": 1,
"created_at": "2021-11-03T11:57:16.748Z",
"updated_at": "2021-11-03T11:57:16.861Z",
"role_ids": [
3
],
"organization_ids": [],
"authorization_ids": [],
"karma_user_ids": [],
"group_ids": {}
},
{
"id": 6,
"organization_id": 3,
"login": "anna@example.com",
"firstname": "Anna",
"lastname": "Lopez",
"email": "anna@example.com",
"image": "4b1cb1fae2e608ffa72099774e1f57ad",
"image_source": null,
"web": "",
"phone": "415-123-5858",
"fax": "",
"mobile": "",
"department": null,
"street": "",
"zip": "",
"city": "",
"country": "",
"address": "Golden Gate Bridge\nSan Francisco, CA 94129",
"vip": false,
"verified": false,
"active": true,
"note": "likes espresso romano - recommended espresso con panna",
"last_login": null,
"source": null,
"login_failed": 0,
"out_of_office": false,
"out_of_office_start_at": null,
"out_of_office_end_at": null,
"out_of_office_replacement_id": null,
"preferences": {},
"updated_by_id": 1,
"created_by_id": 1,
"created_at": "2021-11-03T11:57:16.526Z",
"updated_at": "2021-11-03T11:57:16.611Z",
"role_ids": [
3
],
"organization_ids": [],
"authorization_ids": [],
"karma_user_ids": [],
"group_ids": {}
}
]
Show¶
Required permission: ticket.agent
or admin.user
or
ticket.customer
(shared organization)
Note
Technically, any listings will return user’s own information only.
GET
-Request sent: /api/v1/users/{id}
Response:
# HTTP-Code 200 Ok
{
"id": 11,
"organization_id": null,
"login": "olivia@example.com",
"firstname": "Olivia",
"lastname": "Ross",
"email": "olivia@example.com",
"image": "b6f7a2d56544bb471eb3a3c238c7d964",
"image_source": null,
"web": "",
"phone": "0044 20 1234 5678",
"fax": "",
"mobile": "",
"department": null,
"street": "",
"zip": "",
"city": "",
"country": "",
"address": "Westminster\nLondon SW1A 0AA",
"vip": false,
"verified": false,
"active": true,
"note": "",
"last_login": null,
"source": null,
"login_failed": 0,
"out_of_office": false,
"out_of_office_start_at": null,
"out_of_office_end_at": null,
"out_of_office_replacement_id": null,
"preferences": {},
"updated_by_id": 1,
"created_by_id": 1,
"created_at": "2021-11-03T11:57:17.741Z",
"updated_at": "2021-11-03T11:57:17.794Z",
"role_ids": [
3
],
"organization_ids": [],
"authorization_ids": [],
"karma_user_ids": [],
"group_ids": {}
}
Create¶
Required permission: admin.user
or ticket.agent
Note
🤓 This depends on permissions
Agents can’t set user passwords, roles or group permission. Instead Zammad will apply to default sign up role.
Technically, unauthenticated user creation is possible if you manage to provide the required CSRF token (out of scope of this documentation). If you don’t want that, consider disabling user registration.
Tip
🧐 Creation payloads can be big
Unsure which attributes you can use or set? Run a GET query on any fitting user existing in your instance already.
POST
-Request sent: /api/v1/users
{
"firstname": "Jane",
"lastname": "Doe",
"email": "jdoe@example.com",
"login": "jdoe",
"organization": "Sample Corp.",
"roles": [
"Agent",
"Customer"
]
}
Response:
# HTTP-Code 201 Created
{
"id": 16,
"organization_id": 5,
"login": "jdoe",
"firstname": "Jane",
"lastname": "Doe",
"email": "jdoe@example.com",
"image": null,
"image_source": null,
"web": "",
"phone": "",
"fax": "",
"mobile": "",
"department": null,
"street": "",
"zip": "",
"city": "",
"country": "",
"address": null,
"vip": false,
"verified": false,
"active": true,
"note": "",
"last_login": null,
"source": null,
"login_failed": 0,
"out_of_office": false,
"out_of_office_start_at": null,
"out_of_office_end_at": null,
"out_of_office_replacement_id": null,
"preferences": {
"notification_config": {
"matrix": {
"create": {
"criteria": {
"owned_by_me": true,
"owned_by_nobody": true,
"subscribed": true,
"no": false
},
"channel": {
"email": true,
"online": true
}
},
"update": {
"criteria": {
"owned_by_me": true,
"owned_by_nobody": true,
"subscribed": true,
"no": false
},
"channel": {
"email": true,
"online": true
}
},
"reminder_reached": {
"criteria": {
"owned_by_me": true,
"owned_by_nobody": false,
"subscribed": false,
"no": false
},
"channel": {
"email": true,
"online": true
}
},
"escalation": {
"criteria": {
"owned_by_me": true,
"owned_by_nobody": false,
"subscribed": false,
"no": false
},
"channel": {
"email": true,
"online": true
}
}
}
},
"locale": "en-us"
},
"updated_by_id": 3,
"created_by_id": 3,
"created_at": "2021-11-03T14:42:36.855Z",
"updated_at": "2021-11-03T14:42:36.855Z",
"role_ids": [
2,
3
],
"organization_ids": [],
"authorization_ids": [],
"karma_user_ids": [],
"group_ids": {}
}
Update¶
Required permission: admin.user
or ticket.agent
Note
🤓 This depends on permissions
Agents can’t set user passwords, roles or group permission. Instead Zammad will apply to default sign up role.
PUT
-Request sent: /api/v1/users/{id}
{
"phone": "+49 30 55 57 160 00",
"department": "Sales",
"address": "Marienstr. 18\n10117 Berlin"
}
Response:
# HTTP-Code 200 Ok
{
"id": 16,
"organization_id": 5,
"login": "jdoe",
"firstname": "Jane",
"lastname": "Doe",
"email": "jdoe@example.com",
"image": null,
"image_source": null,
"web": "",
"phone": "+49 30 55 57 160 00",
"fax": "",
"mobile": "",
"department": "Sales",
"street": "",
"zip": "",
"city": "",
"country": "",
"address": "Marienstr. 18\n10117 Berlin",
"vip": false,
"verified": false,
"active": true,
"note": "",
"last_login": null,
"source": null,
"login_failed": 0,
"out_of_office": false,
"out_of_office_start_at": null,
"out_of_office_end_at": null,
"out_of_office_replacement_id": null,
"preferences": {
"notification_config": {
"matrix": {
"create": {
"criteria": {
"owned_by_me": true,
"owned_by_nobody": true,
"subscribed": true,
"no": false
},
"channel": {
"email": true,
"online": true
}
},
"update": {
"criteria": {
"owned_by_me": true,
"owned_by_nobody": true,
"subscribed": true,
"no": false
},
"channel": {
"email": true,
"online": true
}
},
"reminder_reached": {
"criteria": {
"owned_by_me": true,
"owned_by_nobody": false,
"subscribed": false,
"no": false
},
"channel": {
"email": true,
"online": true
}
},
"escalation": {
"criteria": {
"owned_by_me": true,
"owned_by_nobody": false,
"subscribed": false,
"no": false
},
"channel": {
"email": true,
"online": true
}
}
}
},
"locale": "en-us"
},
"updated_by_id": 3,
"created_by_id": 3,
"created_at": "2021-11-03T14:42:36.855Z",
"updated_at": "2021-11-03T14:49:20.018Z",
"role_ids": [
2,
3
],
"organization_ids": [],
"authorization_ids": [],
"karma_user_ids": [],
"group_ids": {}
}
Delete¶
Required permission: admin.user
Danger
⚠ This is a permanent removal
Please note that removing users cannot be undone. Zammad will also remove references - thus potentially tickets!
Removing users with references in e.g. activity streams is not possible
via API - this will be indicated by
"error": "Can't delete, object has references."
. This is not a bug.
Consider using Data Privacy via UI for more control instead.
DELETE
-Request sent: /api/v1/users/{id}
Response:
# HTTP-Code 200 Ok
{}
Organization¶
List¶
Required permission: ticket.agent
or admin.organization
Note
Technically, customers can only see their own organization if applicable.
GET
-Request sent: /api/v1/organizations
Response:
# HTTP-Code 200 Ok
[
{
"id": 1,
"name": "Zammad Foundation",
"shared": true,
"domain": "",
"domain_assignment": false,
"active": true,
"note": "",
"updated_by_id": 1,
"created_by_id": 1,
"created_at": "2023-07-26T08:44:39.608Z",
"updated_at": "2023-08-04T12:02:00.018Z",
"vip": false,
"member_ids": [
2
],
"secondary_member_ids": []
},
{
"name": "Chrispresso Inc.",
"shared": true,
"domain": "",
"domain_assignment": false,
"active": true,
"note": "Manufacturer of individual coffee products.",
"vip": false,
"updated_by_id": 3,
"id": 2,
"created_by_id": 1,
"created_at": "2023-07-26T08:44:48.617Z",
"updated_at": "2023-08-04T12:01:44.370Z",
"member_ids": [
3,
5,
4
],
"secondary_member_ids": []
},
{
"name": "Awesome Customer Inc.",
"shared": true,
"domain": "",
"domain_assignment": false,
"active": true,
"note": "Global distributor of communication and security products, electrical and electronic wire & cable.",
"vip": true,
"updated_by_id": 3,
"id": 3,
"created_by_id": 1,
"created_at": "2023-07-26T08:44:48.632Z",
"updated_at": "2023-08-04T12:54:30.974Z",
"member_ids": [
8,
7,
6
],
"secondary_member_ids": []
},
{
"id": 4,
"name": "Good Customer Inc.",
"shared": true,
"domain": "",
"domain_assignment": false,
"active": true,
"note": "Search the world's information, including webpages, images, videos and more. Good Customer has many special features to help you find exactly what you're looking for.",
"updated_by_id": 1,
"created_by_id": 1,
"created_at": "2023-07-26T08:44:48.645Z",
"updated_at": "2023-07-26T08:44:48.645Z",
"member_ids": [
9
],
"secondary_member_ids": []
}
]
Search¶
Required permission: ticket.agent
or admin.organization
GET
-Request sent:
/api/v1/organizations/search?query=inc&limit=10
Response:
# HTTP-Code 200 Ok
[
{
"name": "Awesome Customer Inc.",
"shared": true,
"domain": "",
"domain_assignment": false,
"active": true,
"note": "Global distributor of communication and security products, electrical and electronic wire & cable.",
"vip": true,
"updated_by_id": 3,
"id": 3,
"created_by_id": 1,
"created_at": "2023-07-26T08:44:48.632Z",
"updated_at": "2023-08-04T12:54:30.974Z",
"member_ids": [
8,
7,
6
],
"secondary_member_ids": []
},
{
"name": "Chrispresso Inc.",
"shared": true,
"domain": "",
"domain_assignment": false,
"active": true,
"note": "Manufacturer of individual coffee products.",
"vip": false,
"updated_by_id": 3,
"id": 2,
"created_by_id": 1,
"created_at": "2023-07-26T08:44:48.617Z",
"updated_at": "2023-08-04T12:01:44.370Z",
"member_ids": [
3,
5,
4
],
"secondary_member_ids": []
},
{
"id": 4,
"name": "Good Customer Inc.",
"shared": true,
"domain": "",
"domain_assignment": false,
"active": true,
"note": "Search the world's information, including webpages, images, videos and more. Good Customer has many special features to help you find exactly what you're looking for.",
"updated_by_id": 1,
"created_by_id": 1,
"created_at": "2023-07-26T08:44:48.645Z",
"updated_at": "2023-07-26T08:44:48.645Z",
"member_ids": [
9
],
"secondary_member_ids": []
}
]
Show¶
Required permission: ticket.agent
or admin.organization
Note
Technically, any users in question can only see their own organization.
GET
-Request sent: /api/v1/organizations/{id}
Response:
# HTTP-Code 200 Ok
{
"id": 2,
"name": "Chrispresso Inc.",
"shared": true,
"domain": "",
"domain_assignment": false,
"active": true,
"note": "Manufacturer of individual coffee products.",
"vip": false,
"updated_by_id": 3,
"created_by_id": 1,
"created_at": "2023-07-26T08:44:48.617Z",
"updated_at": "2023-08-04T12:01:44.370Z",
"member_ids": [
3,
5,
4
],
"secondary_member_ids": []
}
Create¶
Required permission: admin.organization
POST
-Request sent: /api/v1/organizations
{
"name": "Sample Corp.",
"shared": false,
"domain": "example.com",
"domain_assignment": true,
"active": true,
"vip": true,
"note": "Just a sample, aint that nice?",
"members": [
"olivia@example.com",
"david@example.com"
]
}
Response:
# HTTP-Code 201 Created
{
"id": 5,
"name": "Sample Corp.",
"shared": false,
"domain": "example.com",
"domain_assignment": true,
"active": true,
"note": "Just a sample, aint that nice?",
"updated_by_id": 3,
"created_by_id": 3,
"created_at": "2023-08-08T09:12:42.023Z",
"updated_at": "2023-08-08T09:12:42.602Z",
"vip": true,
"member_ids": [
10,
11
],
"secondary_member_ids": []
}
Update¶
Required permission: admin.organization
PUT
-Request sent: /api/v1/organizations/{id}
{
"name": "Sample Corp.",
"shared": false,
"domain": "",
"domain_assignment": false,
"active": true,
"note": "This was a triumph - I'm making a note here - H-U-G-E success!",
"members": [
"olivia@example.com",
"david@example.com"
]
}
Response:
# HTTP-Code 200 Ok
{
"id": 5,
"name": "Sample Corp.",
"shared": false,
"domain": "",
"domain_assignment": false,
"active": true,
"note": "This was a triumph - I'm making a note here - H-U-G-E success!",
"updated_by_id": 3,
"created_by_id": 3,
"created_at": "2023-08-08T09:12:42.023Z",
"updated_at": "2023-08-08T09:16:58.922Z",
"vip": true,
"member_ids": [
10,
11
],
"secondary_member_ids": []
}
Delete¶
Required permission: admin.organization
Danger
⚠ This is a permanent removal
Please note that removing organizations cannot be undone.
Removing organizations with references in e.g. activity streams or users
is not possible via API - this will be indicated by
"error": "Can't delete, object has references."
. This is not a bug.
Consider using Data Privacy via UI for more control instead.
DELETE
-Request sent: /api/v1/organizations/{id}
Response:
# HTTP-Code 200 Ok
{}
Group¶
Note
Please note that
follow_up_possible
may not work as expected. The possible values areyes
ornew_ticket
!If you want to create or update subgroups, use
::
as delimiter for the names. You also have to name the complete hierarchy in the name. Example:Sales::Europe::South
List¶
Required permission: admin.group
GET
-Request sent: /api/v1/groups
Response:
# HTTP-Code 200 Ok
[
{
"id": 1,
"signature_id": 1,
"email_address_id": null,
"name": "Sales",
"assignment_timeout": null,
"follow_up_possible": "yes",
"follow_up_assignment": true,
"active": true,
"note": "Standard Group/Pool for Tickets.",
"updated_by_id": 1,
"created_by_id": 1,
"created_at": "2021-11-03T11:51:13.449Z",
"updated_at": "2021-11-03T11:57:16.357Z",
"user_ids": [
3,
4,
5
]
},
{
"id": 2,
"signature_id": null,
"email_address_id": null,
"name": "2nd Level",
"assignment_timeout": null,
"follow_up_possible": "yes",
"follow_up_assignment": true,
"active": true,
"note": null,
"updated_by_id": 1,
"created_by_id": 1,
"created_at": "2021-11-03T11:57:15.802Z",
"updated_at": "2021-11-03T11:57:16.361Z",
"user_ids": [
3,
4,
5
]
},
{
"id": 3,
"signature_id": null,
"email_address_id": null,
"name": "Service Desk",
"assignment_timeout": null,
"follow_up_possible": "yes",
"follow_up_assignment": true,
"active": true,
"note": null,
"updated_by_id": 1,
"created_by_id": 1,
"created_at": "2021-11-03T11:57:15.807Z",
"updated_at": "2021-11-03T11:57:16.365Z",
"user_ids": [
3,
4,
5
]
}
]
Show¶
Required permission: admin.group
GET
-Request sent: /api/v1/groups/{id}
Response:
# HTTP-Code 200 Ok
{
"id": 2,
"signature_id": null,
"email_address_id": null,
"name": "2nd Level",
"assignment_timeout": null,
"follow_up_possible": "yes",
"follow_up_assignment": true,
"active": true,
"note": null,
"updated_by_id": 1,
"created_by_id": 1,
"created_at": "2021-11-03T11:57:15.802Z",
"updated_at": "2021-11-03T11:57:16.361Z",
"user_ids": [
3,
4,
5
]
}
Create¶
Required permission: admin.group
POST
-Request sent: /api/v1/groups
{
"name": "Amazing Group",
"signature_id": 1,
"email_address_id": 1,
"assignment_timeout": 180,
"follow_up_possible": "new_ticket",
"follow_up_assignment": false,
"active": true,
"note": "Look at my group, my group is amazing!"
}
Response:
# HTTP-Code 201 Created
{
"id": 7,
"signature_id": 1,
"email_address_id": 3,
"name": "Amazing Group",
"assignment_timeout": 180,
"follow_up_possible": "new_ticket",
"follow_up_assignment": false,
"active": true,
"note": "Look at my group, my group is amazing!",
"updated_by_id": 3,
"created_by_id": 3,
"created_at": "2021-11-08T13:09:41.526Z",
"updated_at": "2021-11-08T13:09:41.526Z",
"user_ids": []
}
Update¶
Required permission: admin.group
PUT
-Request sent: /api/v1/groups/{id}
{
"name": "Amazing Group",
"signature_id": 1,
"email_address_id": 3,
"assignment_timeout": 0,
"follow_up_possible": "new_ticket",
"follow_up_assignment": true,
"active": true,
"note": "Look at my group, my group is amazing!"
}
Response:
# HTTP-Code 200 Ok
{
"id": 7,
"signature_id": 1,
"email_address_id": 3,
"name": "Amazing Group",
"assignment_timeout": 0,
"follow_up_possible": "new_ticket",
"follow_up_assignment": true,
"active": true,
"note": "Look at my group, my group is amazing!",
"updated_by_id": 3,
"created_by_id": 3,
"created_at": "2021-11-08T13:09:41.526Z",
"updated_at": "2021-11-08T13:36:24.571Z",
"user_ids": []
}
Delete¶
Required permission: admin.group
Danger
⚠ This is a permanent removal
Please note that removing groups cannot be undone.
Removing organizations with references in e.g. activity streams or tickets
is not possible via API - this will be indicated by
"error": "Can't delete, object has references."
. This is not a bug.
Consider setting affected groups to inactive instead or ensure to move all existing tickets to new groups.
DELETE
-Request sent: /api/v1/groups/{id}
Response:
# HTTP-Code 200 Ok
{}
Roles¶
List¶
Required permission: admin.role
GET
-Request sent: /api/v1/roles
Response:
# HTTP-Code 200 OK
[
{
"id": 1,
"name": "Admin",
"preferences": {},
"default_at_signup": false,
"active": true,
"note": "To configure your system.",
"updated_by_id": 3,
"created_by_id": 1,
"created_at": "2023-07-26T08:44:37.326Z",
"updated_at": "2023-08-08T09:45:15.315Z",
"permission_ids": [
1,
43,
55,
57,
65
],
"knowledge_base_permission_ids": [],
"group_ids": {
"1": [
"full"
],
"2": [
"full"
],
"3": [
"full"
]
}
},
{
"id": 2,
"name": "Agent",
"preferences": {},
"default_at_signup": false,
"active": true,
"note": "To work on Tickets!",
"updated_by_id": 3,
"created_by_id": 1,
"created_at": "2023-07-26T08:44:37.362Z",
"updated_at": "2023-08-22T11:07:16.532Z",
"permission_ids": [
43,
57,
60,
62,
66
],
"knowledge_base_permission_ids": [],
"group_ids": {
"1": [
"full"
],
"2": [
"full"
],
"3": [
"full"
]
}
},
{
"id": 3,
"name": "Service",
"preferences": {},
"default_at_signup": false,
"active": true,
"note": "Changed text",
"updated_by_id": 3,
"created_by_id": 1,
"created_at": "2023-07-26T08:44:37.379Z",
"updated_at": "2023-08-22T11:36:49.504Z",
"permission_ids": [
57,
58
],
"knowledge_base_permission_ids": [],
"group_ids": {
"1": [
"full"
],
"2": [
"full"
],
"3": [
"full"
]
}
}
]
Show¶
Required permission: admin.role
GET
-Request sent: /api/v1/roles/{id}
Response:
# HTTP-Code 200 OK
{
"id": 2,
"name": "Agent",
"preferences": {},
"default_at_signup": false,
"active": true,
"note": "To work on Tickets.",
"updated_by_id": 3,
"created_by_id": 1,
"created_at": "2023-07-26T08:44:37.362Z",
"updated_at": "2023-08-08T09:59:48.202Z",
"permission_ids": [
43,
57,
60,
62,
66
],
"knowledge_base_permission_ids": [],
"group_ids": {
"1": [
"full"
],
"2": [
"full"
],
"3": [
"full"
]
}
}
Create¶
Required permission: admin.role
POST
-Request sent: /api/v1/roles
Request:
{
"active": true,
"default_at_signup": false,
"group_ids": {
"1": "full",
"2": "full",
"3": "full"
},
"id": "c-12",
"name": "VIP service",
"note": "Handling of VIP customers!",
"permission_ids": [
"57",
"58"
]
}
Response:
# HTTP-Code 201 Created
{
"id": 4,
"name": "VIP service",
"preferences": {},
"default_at_signup": false,
"active": true,
"note": "Handling of VIP customers!",
"updated_by_id": 3,
"created_by_id": 3,
"created_at": "2023-08-22T11:24:02.114Z",
"updated_at": "2023-08-22T11:24:02.111Z",
"permission_ids": [
57,
58
],
"knowledge_base_permission_ids": [],
"group_ids": {
"1": [
"full"
],
"2": [
"full"
],
"3": [
"full"
]
}
}
Update¶
Required permission: admin.role
PUT
-Request sent: /api/v1/roles/{id}
Request:
{
"active": true,
"default_at_signup": false,
"group_ids": {
"1": "full",
"2": "full",
"3": "full"
},
"name": "Service",
"note": "Changed text",
"permission_ids": [
"57",
"58"
]
}
Response:
# HTTP-Code 200 OK
{
"updated_at": "2023-08-22T11:36:49.136Z",
"name": "Service",
"default_at_signup": false,
"active": true,
"note": "Changed text",
"updated_by_id": 3,
"id": 3,
"preferences": {},
"created_by_id": 1,
"created_at": "2023-07-26T08:44:37.379Z",
"permission_ids": [
57,
58
],
"knowledge_base_permission_ids": [],
"group_ids": {
"1": [
"full"
],
"2": [
"full"
],
"3": [
"full"
]
}
}
Calendar¶
Note
🤓 Calendars belong to Zammads SLA calculation.
List¶
Required permission: admin.calendar
GET
-Request sent: /api/v1/calendars
Status: 200 Ok
[
{
"id":2,
"name":"Test calendar",
"timezone":"Europe/Berlin",
"business_hours":{
"mon":{
"active":true,
"timeframes":[
[
"09:00",
"17:00"
]
]
},
"tue":{
"active":true,
"timeframes":[
[
"09:00",
"17:00"
]
]
},
"wed":{
"active":true,
"timeframes":[
[
"09:00",
"17:00"
]
]
},
"thu":{
"active":true,
"timeframes":[
[
"09:00",
"17:00"
]
]
},
"fri":{
"active":true,
"timeframes":[
[
"09:00",
"17:00"
]
]
},
"sat":{
"active":false,
"timeframes":[
[
"10:00",
"14:00"
]
]
},
"sun":{
"active":false,
"timeframes":[
[
"10:00",
"14:00"
]
]
}
},
"default":false,
"ical_url":"",
"public_holidays":{
"2021-11-10":{
"active":true,
"summary":"Feast day 1"
},
"2021-11-11":{
"active":true,
"summary":"Feast day 2"
}
},
"last_log":null,
"last_sync":"2021-11-10T13:14:20.835Z",
"updated_by_id":3,
"created_by_id":3,
"created_at":"2021-11-10T13:14:20.835Z",
"updated_at":"2021-11-10T13:14:20.835Z"
}
]
Show¶
Required permission: admin.calendar
GET
-Request sent: /api/v1/calendars/{id}
Status: 200 Ok
{
"id":2,
"name":"Test calendar",
"timezone":"Europe/Berlin",
"business_hours":{
"mon":{
"active":true,
"timeframes":[
[
"09:00",
"17:00"
]
]
},
"tue":{
"active":true,
"timeframes":[
[
"09:00",
"17:00"
]
]
},
"wed":{
"active":true,
"timeframes":[
[
"09:00",
"17:00"
]
]
},
"thu":{
"active":true,
"timeframes":[
[
"09:00",
"17:00"
]
]
},
"fri":{
"active":true,
"timeframes":[
[
"09:00",
"17:00"
]
]
},
"sat":{
"active":false,
"timeframes":[
[
"10:00",
"14:00"
]
]
},
"sun":{
"active":false,
"timeframes":[
[
"10:00",
"14:00"
]
]
}
},
"default":false,
"ical_url":"",
"public_holidays":{
"2021-11-10":{
"active":true,
"summary":"Feast day 1"
},
"2021-11-11":{
"active":true,
"summary":"Feast day 2"
}
},
"last_log":null,
"last_sync":"2021-11-10T13:14:20.835Z",
"updated_by_id":3,
"created_by_id":3,
"created_at":"2021-11-10T13:14:20.835Z",
"updated_at":"2021-11-10T13:14:20.835Z"
}
Create¶
Required permission: admin.calendar
POST
-Request sent: /api/v1/calendars
{
"name":"Test calendar",
"timezone":"Europe/Berlin",
"business_hours":{
"mon":{
"active":true,
"timeframes":[
[
"09:00",
"17:00"
]
]
},
"tue":{
"active":true,
"timeframes":[
[
"09:00",
"17:00"
]
]
},
"wed":{
"active":true,
"timeframes":[
[
"09:00",
"17:00"
]
]
},
"thu":{
"active":true,
"timeframes":[
[
"09:00",
"17:00"
]
]
},
"fri":{
"active":true,
"timeframes":[
[
"09:00",
"17:00"
]
]
},
"sat":{
"active":false,
"timeframes":[
[
"10:00",
"14:00"
]
]
},
"sun":{
"active":false,
"timeframes":[
[
"10:00",
"14:00"
]
]
}
},
"ical_url":"",
"public_holidays":{
"2021-11-10":{
"active":true,
"summary":"Feast day 1"
},
"2021-11-11":{
"active":true,
"summary":"Feast day 2"
}
},
"note":"",
"id":"c-1"
}
Response:
Status: 201 Created
{
"id":2,
"name":"Test calendar",
"timezone":"Europe/Berlin",
"business_hours":{
"mon":{
"active":true,
"timeframes":[
[
"09:00",
"17:00"
]
]
},
"tue":{
"active":true,
"timeframes":[
[
"09:00",
"17:00"
]
]
},
"wed":{
"active":true,
"timeframes":[
[
"09:00",
"17:00"
]
]
},
"thu":{
"active":true,
"timeframes":[
[
"09:00",
"17:00"
]
]
},
"fri":{
"active":true,
"timeframes":[
[
"09:00",
"17:00"
]
]
},
"sat":{
"active":false,
"timeframes":[
[
"10:00",
"14:00"
]
]
},
"sun":{
"active":false,
"timeframes":[
[
"10:00",
"14:00"
]
]
}
},
"default":false,
"ical_url":"",
"public_holidays":{
"2021-11-10":{
"active":true,
"summary":"Feast day 1"
},
"2021-11-11":{
"active":true,
"summary":"Feast day 2"
}
},
"last_log":null,
"last_sync":"2021-11-10T13:14:20.835Z",
"updated_by_id":3,
"created_by_id":3,
"created_at":"2021-11-10T13:14:20.835Z",
"updated_at":"2021-11-10T13:14:20.835Z"
}
Update¶
Required permission: admin.calendar
PUT
-Request sent: /api/v1/calendars/{id}
{
"name":"Test calendar Update",
"timezone":"Europe/Berlin",
"default":false,
"business_hours":{
"mon":{
"active":true,
"timeframes":[
[
"09:00",
"17:00"
]
]
},
"tue":{
"active":true,
"timeframes":[
[
"09:00",
"17:00"
]
]
},
"wed":{
"active":true,
"timeframes":[
[
"09:00",
"17:00"
]
]
},
"thu":{
"active":true,
"timeframes":[
[
"09:00",
"17:00"
]
]
},
"fri":{
"active":true,
"timeframes":[
[
"09:00",
"17:00"
]
]
},
"sat":{
"active":false,
"timeframes":[
[
"10:00",
"14:00"
]
]
},
"sun":{
"active":false,
"timeframes":[
[
"10:00",
"14:00"
]
]
}
},
"ical_url":"",
"public_holidays":{
"2021-11-10":{
"active":true,
"summary":"Feast day 1"
},
"2021-11-11":{
"active":true,
"summary":"Feast day 2"
}
},
"note":"",
"id":2
}
Response:
Status: 200 Ok
{
"id":2,
"name":"Test calendar Update",
"timezone":"Europe/Berlin",
"default":false,
"ical_url":"",
"business_hours":{
"mon":{
"active":true,
"timeframes":[
[
"09:00",
"17:00"
]
]
},
"tue":{
"active":true,
"timeframes":[
[
"09:00",
"17:00"
]
]
},
"wed":{
"active":true,
"timeframes":[
[
"09:00",
"17:00"
]
]
},
"thu":{
"active":true,
"timeframes":[
[
"09:00",
"17:00"
]
]
},
"fri":{
"active":true,
"timeframes":[
[
"09:00",
"17:00"
]
]
},
"sat":{
"active":false,
"timeframes":[
[
"10:00",
"14:00"
]
]
},
"sun":{
"active":false,
"timeframes":[
[
"10:00",
"14:00"
]
]
}
},
"public_holidays":{
"2021-11-10":{
"active":true,
"summary":"Feast day 1"
},
"2021-11-11":{
"active":true,
"summary":"Feast day 2"
}
},
"updated_by_id":3,
"last_log":null,
"last_sync":"2021-11-10T13:23:07.455Z",
"created_by_id":3,
"created_at":"2021-11-10T13:14:20.835Z",
"updated_at":"2021-11-10T13:23:07.455Z"
}
Delete¶
Required permission: admin.calendar
Danger
⚠ This is a permanent removal
Please note that removing Calendar configurations cannot be undone.
Removing calendars with references in SLA configurations
is not possible via API - this will be indicated by
"error": "Can't delete, object has references."
. This is not a bug.
DELETE
-Request sent: /api/v1/calendars/{id}
Status: 200 Ok
{}
Service-Level Agreements (SLA)¶
Note
🤓 SLAs depend on Zammads Calendars.
List¶
Required permission: admin.sla
GET
-Request sent: /api/v1/slas
Response:
# HTTP-Code 200 Ok
[
{
"id":2,
"calendar_id":1,
"name":"new sla",
"first_response_time":120,
"response_time":null,
"update_time":120,
"solution_time":120,
"condition":{
"ticket.state_id":{
"operator":"is",
"value":"2"
}
},
"updated_by_id":3,
"created_by_id":3,
"created_at":"2021-11-10T12:54:39.368Z",
"updated_at":"2021-11-10T12:54:39.368Z"
}
]
Show¶
Required permission: admin.sla
GET
-Request sent: /api/v1/slas/{id}
Response:
# HTTP-Code 200 Ok
{
"id":2,
"calendar_id":1,
"name":"new sla",
"first_response_time":120,
"response_time":null,
"update_time":120,
"solution_time":120,
"condition":{
"ticket.state_id":{
"operator":"is",
"value":"2"
}
},
"updated_by_id":3,
"created_by_id":3,
"created_at":"2021-11-10T12:54:39.368Z",
"updated_at":"2021-11-10T12:54:39.368Z"
}
Create¶
Required permission: admin.sla
POST
-Request sent: /api/v1/slas
{
"name":"new sla",
"first_response_time":"120",
"response_time":"",
"update_time":"120",
"solution_time":"120",
"condition":{
"ticket.state_id":{
"operator":"is",
"value":"2"
}
},
"calendar_id":"1",
}
Response:
# HTTP-Code 201 Created
{
"id":2,
"calendar_id":1,
"name":"new sla",
"first_response_time":120,
"response_time":null,
"update_time":120,
"solution_time":120,
"condition":{
"ticket.state_id":{
"operator":"is",
"value":"2"
}
},
"updated_by_id":3,
"created_by_id":3,
"created_at":"2021-11-10T12:54:39.368Z",
"updated_at":"2021-11-10T12:54:39.368Z"
}
Update¶
Required permission: admin.sla
PUT
-Request sent: /api/v1/slas/{id}
{
"name":"update sla",
"first_response_time":"120",
"response_time":"",
"update_time":"120",
"solution_time":"120",
"condition":{
"ticket.state_id":{
"operator":"is",
"value":"2"
}
},
"calendar_id":"1",
"id":2
}
Response:
# HTTP-Code 200 Ok
{
"id":2,
"calendar_id":1,
"name":"update sla",
"first_response_time":120,
"response_time":null,
"update_time":120,
"solution_time":120,
"condition":{
"ticket.state_id":{
"operator":"is",
"value":"2"
}
},
"updated_by_id":3,
"created_by_id":3,
"created_at":"2021-11-10T12:54:39.368Z",
"updated_at":"2021-11-10T13:02:52.053Z"
}
Delete¶
Required permission: admin.sla
Danger
⚠ This is a permanent removal
Please note that removing SLA configurations cannot be undone.
DELETE
-Request sent: /api/v1/slas/{id}
Response:
# HTTP-Code 200 Ok
{}
Ticket Endpoints¶
Zammad comes with many ticket related endpoints. For better overview, they have been splitted in different sections:
Articles¶
General information about ticket articles¶
Some attributes of articles might not be straight forward or come with fairly many options - below list hopefully helps you on this journey.
- content_type
Zammad supports
text/html
for HTML formatted text ortext/plain
for plain text. This allows you to have better formatting options if you need them.Zammad web UI usually uses
text/html
.- type
Zammad supports a huge number of article types. Below list may be incomplete depending on your instance and possibly installed add-ons / custom changes.
If not stated otherwise, all article types below are communication articles and thus affecting SLA calculation in Zammad defaults.
The difference is that communication articles provide the option to reply automatically. Which actions exactly are available depends on the article type and e.g. recipient lists.
This allows you to create incoming or outgoing email articles.
However, this highly depends on the chosen
sender
.phone
Indicates phone notes.
web
Usually used by customers only. This type is being used when ever your customer uses the web UI to create articles.
note
When ever a communication does not fit (e.g.: internal notes) choose note. Zammad also uses this article type as default fall back.
This is not a communication article.
sms
This type is being used for Zammads SMS integration.
chat
This article type is technically a place holder and is only available via API.
fax
This article type is technically a place holder and is only available via API.
twitter status
&twitter direct-message
These articles types are used by Zammads twitter channel. Technically you can use these to automatically respond to existing requests via twitter.
facebook feed post
&facebook feed comment
These articles types are used by Zammads facebook channel. Technically you can use these to automatically respond to existing requests via facebook.
telegram personal-message
Used by Zammads Telegram channel. Technically you can use these to automatically respond to existing requests via Telegram.
- internal
This attribute allows you to set the visibility of your articles. For internal visible only use
true
, for visibly for your customers as well usefalse
.Warning
🔒 Visibility: internal doesn’t mean it’s silent
If you set an article to
internal: true
but choose to send an email, please be aware that said Email is still being sent out!- sender
Indicates which use did create the article. You can choose from:
Agent
Customer
System
Warning
Depending of above selection, some article types may not be available or behave different. Please be aware that
System
causes users not being able to read the bodies (this works similar to Zammads trigger displaying in tickets).List Articles by Ticket¶
Required permission:
ticket.agent
orticket.customer
GET
-Request sent:/api/v1/ticket_articles/by_ticket/{ticket id}
Response:
# HTTP-Code 200 OK [ { "id": 9, "ticket_id": 5, "type_id": 1, "sender_id": 2, "from": "David Bell <david@example.com>", "to": "order@chrispresso.com", "cc": null, "subject": null, "reply_to": null, "message_id": null, "message_id_md5": null, "in_reply_to": null, "content_type": "text/html", "references": null, "body": "Hi,<br>\n<br>\n<u>please send me:</u><br>\n1 x Café Kopi susu<br>\n4 x Viennese melange<br>\n<br>\n<u>Delivery Address:</u><br>\nDavid Bell<br>\nEiffel Tower<br>\n5 Avenue Anatole France<br>\n75007 Paris<br>\n<span class=\"js-signatureMarker\"></span>\n<br>\nDavid Bell", "internal": false, "preferences": {}, "updated_by_id": 10, "created_by_id": 10, "origin_by_id": null, "created_at": "2021-08-02T11:57:18.068Z", "updated_at": "2021-08-02T11:57:18.068Z", "attachments": [], "type": "email", "sender": "Customer", "created_by": "david@example.com", "updated_by": "david@example.com", "time_unit": null }, { "id": 10, "ticket_id": 5, "type_id": 1, "sender_id": 1, "from": "Emma Taylor via <order@chrispresso.com>", "to": "David Bell <david@example.com>", "cc": null, "subject": null, "reply_to": null, "message_id": null, "message_id_md5": null, "in_reply_to": null, "content_type": "text/html", "references": null, "body": "Hi David,<br>\n<br>\nnice, we will ship it to your delivery address:<br>\n<br>\nEiffel Tower\n5 Avenue Anatole France\n75007 Paris.<br>\n<br>\nYou will get it till Wednesday.<br>\n<span class=\"js-signatureMarker\"></span>\n<br>\n--<br>\nGreetings,<br>\n<br>\nEmma Taylor<br>", "internal": false, "preferences": {}, "updated_by_id": 5, "created_by_id": 5, "origin_by_id": null, "created_at": "2021-08-03T09:57:18.121Z", "updated_at": "2021-08-03T09:57:18.121Z", "attachments": [], "type": "email", "sender": "Agent", "created_by": "emma@chrispresso.com", "updated_by": "emma@chrispresso.com" } ]List specific article¶
Required permission:
ticket.agent
orticket.customer
GET
-Request sent:/api/v1/ticket_articles/{article id}
Response:
# HTTP-Code 200 OK { "id": 9, "ticket_id": 5, "type_id": 1, "sender_id": 2, "from": "David Bell <david@example.com>", "to": "order@chrispresso.com", "cc": null, "subject": null, "reply_to": null, "message_id": null, "message_id_md5": null, "in_reply_to": null, "content_type": "text/html", "references": null, "body": "Hi,<br>\n<br>\n<u>please send me:</u><br>\n1 x Café Kopi susu<br>\n4 x Viennese melange<br>\n<br>\n<u>Delivery Address:</u><br>\nDavid Bell<br>\nEiffel Tower<br>\n5 Avenue Anatole France<br>\n75007 Paris<br>\n<span class=\"js-signatureMarker\"></span>\n<br>\nDavid Bell", "internal": false, "preferences": {}, "updated_by_id": 10, "created_by_id": 10, "origin_by_id": null, "created_at": "2021-08-02T11:57:18.068Z", "updated_at": "2021-08-02T11:57:18.068Z", "attachments": [], "type": "email", "sender": "Customer", "created_by": "david@example.com", "updated_by": "david@example.com", "time_unit": null }Create¶
Required permission:
ticket.agent
orticket.customer
Tip
If you want to create articles on behalf of other users (e.g. for a phone note), use the
origin_by_id
attribute.ticket.agent
permission is mandatory for this.
POST
-Request sent:/api/v1/ticket_articles
{ "ticket_id": 5, "subject": "Call note", "body": "Called the customer and discussed their issues.<br/>Turns out these were caused by invalid configurations - solved.", "content_type": "text/html", "type": "phone", "internal": false, "sender": "Agent", "time_unit": "15" }Response:
# HTTP-Code 201 Created { "id": 33, "ticket_id": 5, "type_id": 5, "sender_id": 1, "from": "Christopher Miller", "to": null, "cc": null, "subject": "Call note", "reply_to": null, "message_id": null, "message_id_md5": null, "in_reply_to": null, "content_type": "text/html", "references": null, "body": "Called the customer and discussed their issues.<br>Turns out these were caused by invalid configurations - solved.", "internal": false, "preferences": {}, "updated_by_id": 3, "created_by_id": 3, "origin_by_id": null, "created_at": "2021-11-08T16:13:35.962Z", "updated_at": "2021-11-08T16:13:35.962Z", "attachments": [], "type": "phone", "sender": "Agent", "created_by": "chris@chrispresso.com", "updated_by": "chris@chrispresso.com" }Hint
The first attachment example does work, remove the second “generalized” part to try the payload out. 🔨
{ "ticket_id": 5, "to": "", "cc": "", "subject": "some subject", "body": "Please see attached file...", "content_type": "text/plain", "type": "note", "internal": true, "time_unit": "25", "attachments": [ { "filename": "portal.txt", "data": "VGhlIGNha2UgaXMgYSBsaWUhCg==", "mime-type": "text/plain" }, { "filename": "{filename}", "data": "{file content base64 encoded}", "mime-type": "{attachments mime-type}" } ] }Response:
# HTTP-Code 201 Created { "id": 35, "ticket_id": 5, "type_id": 10, "sender_id": 1, "from": "Christopher Miller", "to": "", "cc": "", "subject": "some subject", "reply_to": null, "message_id": null, "message_id_md5": null, "in_reply_to": null, "content_type": "text/plain", "references": null, "body": "Please see attached file...", "internal": true, "preferences": {}, "updated_by_id": 3, "created_by_id": 3, "origin_by_id": null, "created_at": "2021-11-09T12:02:55.434Z", "updated_at": "2021-11-09T12:02:55.434Z", "attachments": [ { "id": 17, "filename": "portal.txt", "size": "19", "preferences": { "Mime-Type": "text/plain" } } ], "type": "note", "sender": "Agent", "created_by": "chris@chrispresso.com", "updated_by": "chris@chrispresso.com" }Inline images can be used by providing data URIs in your HTML markup.
{ "ticket_id": 5, "to": "", "cc": "", "subject": "some subject", "body": "Let's see the <b>phoenix</b> <img src=\"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAEYAAAA9CAYAAAAQyx+GAAAABmJLR0QA/wD/AP+gvaeTAAAACXBIWXMAAAsTAAALEwEAmpwYAAAAB3RJTUUH4AsMEREh5cLFggAADiRJREFUeNrtm3t0VdWdxz/7nPtI7k3uvUluHsgjgAR0AJMCBR0ZwVpRLBa76gwzPlBqa8FFO8WZNQO6cNWpS2hnupwqta0dnWV0Qp0RKmtg1WJ56qCBAZNWqLxJQiCQJ3nc1zln7/njnktubh7cVAggs9fKWuee5z6f8/399u/32zswFK28cirllaWUV+pJ+7i2WwJAeeVKyit/RHllLuWV2v8D6gnnZcorj1NeeQ/llZ4rGY42pE9bOOMJYBewCXiJ8spxlFfqLJxxxQESQ6qahTOw/cyvgXuBOuAZ4L9ZOKO5B5wErM89mJ5w8oD/Am63j/wKWAN8xMIZVo9zrwkwPSFdD1QA0+09NcDPgDdYOOPU5VbPRfcxD6xaM/D+7pc9CnwH2G+fUgysAn5JeeVXzwOJK+zzoZgHVq3RgBuBkcDuihVLW5KPZ7+2k45v3JYANddWSnHSKSdtU3uR33x8grWLhxyM4xLdVwFngBHAiw+sWtMFvAdsr1ixtOk8FEBX8l1LaCuBF4A8e/cIYBlQxLjCRUB0c22IOaM8V69iHli1hooVSxPbbuAm4Algvq2ET4DNwO8qViw9meRzngSeAzK78aqDL08dt+qJScHXr3ow/QAKAHcCLwGFQCvQAhwFNmVIY8NrTy+r4c29zyCtp0G5AJDyaMWtf/bboNu5bs4oz9ahhHNJR6UUOLoNZSWwOMnkQkBYoA5FNcf2rcEbZre4sv4cAMs6/dMpYz8sCWSVzBnluelzN1z3YV63Aq8Co1P8kqEQerszQ6/PCLA/I3ju+5PHnJw2LDhRwLfnjPK8MlSqGZKUoGLF0vPDdcWKpVFgK/AF4F9TPpJLoHS/Eeam9npGRFq94ZhRYh//webakPtSQdlbHLw8uVKSYhK/2ypWLF0GfCkpljnf2g0Lq+64I2aYLhQAQeDZiwkiAWNvcZCpNU3sLQ7evbc4OH738EJx2SLfZPN65Icv+wwpvy+VWhwyrMxDzef4tLGVvLwCHpl/H3eNH43d03Zg8pxRntrBmlTDLCja0Scg3Y6hVgMy2x9e4svpCjsuC5W/WXYeCsDrW/dF8Gb8LJiTNbo9anwtZligJH6fH1Mm3I8A8ALPAw/NGeUhAae/l05uyccbZqLlB2H/J5k+w2AR8E9CKLcvJ3R/li/8LeDI5QGz9oUEIC8wHvgrYHFTVySAEKAJMBV+nx+pelypA/M214Zmzhnl+SD5pRtm4QTygYwkN6ELgS4EDgS6krgMgwxLEWmLTnRpzs5FGF2LlBL4ckJ/yMoOlwMri3aw3jGUKkkCkgVMtoO+x2z/AcmWrSR+nw9LqdQ7+YCngbnJqhGCLOAOe8SbaIcGXiXxmlLLkpamS6lAuD52eSd+aorpxbqreobsOECGR9V6syJeobG0cDtvXsqUoDeQtS8kFDId+ArwMFDQM5FQIGUiiDCys30OUymhesYVArh5c23owTmjPP+R8DOF22ndMzL/ndxgR6amq9s0XZYoKTAMHSOqYxjaHt01/kDOyHsaXd7gVyMdR8cbkWY0XYWyA6F63SF/VLiddxL+yDFEQALAbLs4db/91QG67Ci4FSkbyfbnEMibhO7odLY1VWR5sh40LBno4+4BUN/6YOWzuzwvPhtQgdzrgTGgSlqbs8eh8OlOC2lpWKZV68zI/TB3xKxzWXmTx0gZeUSpMLFwA0akmUyP9YHTZf5LwbO8x6xuJy0usVrcdrxyCzANyAHqgdNAsw2lGWih7mgdD353CZ7sp3A6/+f2grxnRoXPlVtSDn+orASt9wDa6Thy4Oe5d02rtK4Lft0uehUCSEMgdFMixIbAsFtD/sKbvUJ3fllJMwsE0orSWr+Nzqa9O7ID/OOEP3RWpo5cl9qUFNAB7LOz62bgHGtfCPU686Owh8OfjEEpMIwjmR0tJehahlQKS6m+wGSZY8bfdu6nb6z3P/7wSmt0cKTVpeWgqbE500JPWmcmb/flzcxxeQonK2mOU9K03ZiGGW3GCJ9tVIo1CSinbxMU7VCXvOyQMKVYX8FbD1NLlDAP/X4aMNXmecQrxHUK3CAwLIlT6yMW1R1To/MW3FHGw89xoumI2sbo0CHuURZZsX2TyqymvBuVNHp9KyPaSjTU8FYs3LkB4OMxeQzb2TxEkW9iBLrQ8e6yZZk9dIPkiFsTwwG3AAyp+ruLriz19cqqd+fEXuZ74SNsFBqP6plk64GaG0H2Sg1NoxMr1r4v0t665tZmjL3FQb5wvHnIClWDrf/mA92FXSmPO2CEBCeAacl+L5Waa5Kn649vGG5nppBGNoCyQC84BEdm97ZtZbUYsfZ/m9nGwcueRA4AJLFVAtycSJM0p+5IquZhDABGk4ajueDegg7/9GxNdns2LbcGocd6mZHu8BwVyvpVco505YHpNqOJwFh7+8R4t55nh/8gwJRW/3UTZRLxXE/jsPuJuf2guqMdLe9EamUlIoS+ZcLH77cC/UK5/GDiqikEZiV91OOFDi1PwfkM0ZBywLhCyCgNIxbR5ZvSfZ4CPf9wN6h4qwFeBKi/8ztcuaYUbyPt4C/xRjUBTQsmFCMAw1IDV9uUxHL4OT3iG8Tc+d2eOXg4+bQwsDZ/w1OnG+c/z/D3XuJK9jHCNqPhyYrxaiJPgUchcKgYwoxcuOJmRTh73UN0+qfFcSrQfI2IzI6EOZ0EfgyQv+GpK6OCN4DTDQJ3p/jHWqfmyBdIj182cXfoVW5u+Q1KXKirCpDUjf17DHdBijmJKPBy/oanOhvnP//ZS5uqZvWAvy+C0y0E7krqTgzLFck3Tw0ri27jydZv8pcdv8AfrUel8zIySmv+l2gN3glCAwWOgoMg9WMJ35KOWi4IRhQvR9WszlA1q92J3+kATFM1up1H5cSl7rQKjXNHfxF6Ze7jrX83YXHbk4wwjhEToJld6RexTYOacSuJuQrjO9ztUpnu5/M3PCXTVUu6AZ4bGKtqVo+2QZ4BGu0EsEUULzcHAtbrmJ0CCMhWcD/Cgcs0Wu6K7m5bFn1Lu8W5a4luRnVLgClAKNCtjvSnPZRJKGs8DSMfY9Th5zDrpnxc8O533xyMWtICI4qXn1M1q0PEp02n2M5SASeAOlWzugE4m/TXCJwRxctjfQKzzUg5PAWY5sypXUePLzHXGwvUpoA381xBRANTpaqgk8EUAnQrQv3YvyV3/x4lTsx4GtbTOP/5QYER6fiYxAuqmtVFdpVsHnBfUhkxbANJgGm0ldUAnEr6Oy2Kl3cBlD23+Y65/vfWPqbejl3vPF4QdeLsKyUSQJv7Hn5/+68RygIhUCKp64K4PxEaSggQGsK0cHRF8B3+5MgXv3JzyZ/iBtP6DH0AygW+aFfiFvSqxHU3g/hUbEvC9BDOFhmtO3Xq6J7AsKaDj0JrRiymwJAoaVc3RXfvhIBO11/wv3N3xs0qbKCHYggpO0Fv1Ixoh6O9UTnazwpn62nd2XLSo0U6O2NFN2xumnnvpjvHZW4brFpgkIWqPgD57NrtPcAiYNjAT3NC7ChE9luYzV3ETJ+07HKmZaEMExWNIWMGxGIow0AZRku4ruTEyawnpKvtFCLcjBZp0fRwW6YW6cjSol1uLRpyabGQVxixQ3rEWOdq7tzo/yMHBHRtOdzGHSWBS6OYNIB57ERwHoglCDEcVLyGe37wkxDeB7EaUJFuOfRR81VSEZcPtL9+woh+Gg2pmEMJMxo/R8RnLRHnV0ZUo3gVxfsoagt3cX49zp+ils8Mpq9Rxzz2bElXR8M/e7wF8x3uLFACrDYI7QazCbAu/FgBODQ6KmoJf9SCMvrMriXxxUWvAVVAW9EOYskTbKnzSUOuGICDG0vHA98E7hNCK9Y0pys7MIa8HC+6cQhkZ/o9cmp0rK0jvKu5LyhniC9kXGs79GjRju6KVDqTbxcdzMGNpQBMmFed+O21p0EetYvdiUkxhID8gIbfC0Ko9HujCzrX1RPa2ZQKZYcdvW4CTMBKAPis6hgUmIMbS88DSIZiX/Nl4hNlXwNcqRmLywGFOQJPxiB7owk6N50m9LuzqJgEQQfwBvCToh0culQQLggmFYa9T7e9ZwnwoA2ksL8bejOhICBwOgE1uJ6EtpyVnZsapIrK/Wi8Avx70Q7CF9NE0u5OqnnYMDTihaI8YI7tO6anCMO0PakJWJpGRsCLO+gXcYyDg2KEK1taO96u3ynbrJeGVbIz+fBQQ+mhmIMbSwXgB3KBG4jPGM4DAsSXX4SAKBCxnV4dUAuc9HnJzvGJBW4ntwwKCLQjOC07zHc7tzS+4H/mTM3lhNELjA0lSHw27267cHQcOGYDOEV8BrF+wrzqcA+fsr9sNhY/RjElzWdKu2h0GFiH4i1RVtUSD2POcWa2/7ICSU0iNeLzydsmzKv+z7RimOqyIuBhTJ6zHbC6wCjXDhwC9gDrRGnVlqR7xb+S8HOlNNGPw+21T1WXIUqrEtvTiS9Q/us0nnHCDsC2AptEadWxvu55pTWRpjoQpVWo6jIBPAT8AzBpgEssYDfwAbAd2CpKqyJXA5D0yg4JicehDLNV8m26l3Gktmbgt8A24ENRWrW/P9Vd6U1cSCX29kxgBTC3n2uqgHeAncAnorSqsS+4V1MTaUB5HHgSmNBHWf5tYJ0NpuZqM5e0waSYTi7xdbULU0ynBii3FXJSlFadvdrVMSCYFJWUAj8Bbks6Zwvwc+AjoEWUVoU+L+rou6ie8pVVddkCO4stsGOPcuCXxP9TJCJKq6zPM5C+FOMBfmD7kwPE1/mvt+u1iNIqdS0ASVaMID6x/pqdB/1QlFa9fy1B6G9YnqSqy76nqstG9uWIr9X2f3YvQamazNcfAAAAAElFTkSuQmCC\">", "content_type": "text/html", "type": "note", "internal": false, "time_unit": "12" }Response:
# HTTP Code 201 Created { "id": 37, "ticket_id": 5, "type_id": 10, "sender_id": 1, "from": "Christopher Miller", "to": "", "cc": "", "subject": "some subject", "reply_to": null, "message_id": null, "message_id_md5": null, "in_reply_to": null, "content_type": "text/html", "references": null, "body": "Let's see the <b>phoenix</b> <img src=\"/api/v1/ticket_attachment/5/37/19?view=inline\" style=\"max-width:100%;\">", "internal": false, "preferences": {}, "updated_by_id": 3, "created_by_id": 3, "origin_by_id": null, "created_at": "2021-11-09T12:10:49.375Z", "updated_at": "2021-11-09T12:10:49.375Z", "attachments": [ { "id": 19, "filename": "image1.png", "size": "3735", "preferences": { "Content-Type": "image/png", "Mime-Type": "image/png", "Content-ID": "5.e384b84e-bfef-49f7-af22-8546fb99f8dc@fqdn", "Content-Disposition": "inline" } } ], "type": "note", "sender": "Agent", "created_by": "chris@chrispresso.com", "updated_by": "chris@chrispresso.com" }Receive attachments¶
Now that you have all those fancy attachments within your tickets, you may want to download specific ones.
GET
-Request sent:/api/v1/ticket_attachment/{ticket id}/{article id}/{attachment id}
Response:
![]()
Hint
If you’re not sure which articles a ticket / article contains, please retrieve affected articles first.
Linking Tickets¶
Get¶
Required permission:
ticket.agent
oradmin
GET
-Request sent:/api/v1/links
{ "link_object": "Ticket", "link_object_value": "5" }Response:
# HTTP-Code 200 Ok { "links": [ { "link_type": "normal", "link_object": "Ticket", "link_object_value": 41 } ], "assets": { "Ticket": { "41": { "id": 41, "group_id": 2, "priority_id": 2, "state_id": 4, "organization_id": 1, "number": "93039", "title": "test", "owner_id": 1, "customer_id": 2, "note": null, "first_response_at": null, "first_response_escalation_at": null, "first_response_in_min": null, "first_response_diff_in_min": null, "close_at": "2023-08-04T14:37:07.884Z", "close_escalation_at": null, "close_in_min": null, "close_diff_in_min": null, "update_escalation_at": null, "update_in_min": null, "update_diff_in_min": null, "last_close_at": "2023-08-04T14:37:07.883Z", "last_contact_at": "2023-08-04T12:02:00.036Z", "last_contact_agent_at": null, "last_contact_customer_at": "2023-08-04T12:02:00.036Z", "last_owner_update_at": null, "create_article_type_id": 5, "create_article_sender_id": 2, "article_count": 1, "escalation_at": null, "pending_time": null, "type": null, "time_unit": null, "preferences": {}, "updated_by_id": 3, "created_by_id": 3, "created_at": "2023-08-04T12:01:59.897Z", "updated_at": "2023-08-08T09:24:43.977Z", "article_ids": [ 64, 63 ], "ticket_time_accounting_ids": [] } }, "Group": { "2": { "id": 2, "signature_id": null, "email_address_id": 1, "name": "2nd Level", "assignment_timeout": null, "follow_up_possible": "yes", "reopen_time_in_days": null, "follow_up_assignment": true, "active": true, "shared_drafts": true, "note": "", "updated_by_id": 3, "created_by_id": 1, "created_at": "2023-07-26T08:44:48.589Z", "updated_at": "2023-07-27T13:04:25.495Z", "user_ids": [ 3, 4, 5 ] }, "3": { "id": 3, "signature_id": null, "email_address_id": 1, "name": "Service Desk", "assignment_timeout": null, "follow_up_possible": "yes", "reopen_time_in_days": null, "follow_up_assignment": true, "active": true, "shared_drafts": true, "note": "", "updated_by_id": 3, "created_by_id": 1, "created_at": "2023-07-26T08:44:48.602Z", "updated_at": "2023-07-26T09:28:36.505Z", "user_ids": [ 3, 4, 5 ] }, "1": { "id": 1, "signature_id": 1, "email_address_id": 1, "name": "Sales", "assignment_timeout": null, "follow_up_possible": "yes", "reopen_time_in_days": null, "follow_up_assignment": true, "active": true, "shared_drafts": true, "note": "Standard Group/Pool for Tickets.", "updated_by_id": 3, "created_by_id": 1, "created_at": "2023-07-26T08:44:38.651Z", "updated_at": "2023-07-26T09:31:54.224Z", "user_ids": [ 3, 4, 5 ] } }, "User": { "1": { "id": 1, "organization_id": null, "login": "-", "firstname": "-", "lastname": "", "email": "", "image": null, "image_source": null, "web": "", "phone": "", "fax": "", "mobile": "", "department": "", "street": "", "zip": "", "city": "", "country": "", "address": "", "vip": false, "verified": false, "active": false, "note": "", "last_login": null, "source": null, "login_failed": 0, "out_of_office": false, "out_of_office_start_at": null, "out_of_office_end_at": null, "out_of_office_replacement_id": null, "preferences": {}, "updated_by_id": 1, "created_by_id": 1, "created_at": "2023-07-26T08:44:37.217Z", "updated_at": "2023-07-26T08:44:37.217Z", "role_ids": [], "two_factor_preference_ids": [], "organization_ids": [], "authorization_ids": [], "overview_sorting_ids": [], "group_ids": {} }, "3": { "id": 3, "organization_id": 2, "login": "chris@chrispresso.com", "firstname": "Christopher", "lastname": "Miller", "email": "chris@chrispresso.com", "image": "7a6a0d1d94ad2037153cf3a6c1b49a53", "image_source": null, "web": "", "phone": "", "fax": "", "mobile": "", "department": null, "street": "", "zip": "", "city": "", "country": "", "address": null, "vip": false, "verified": false, "active": true, "note": "", "last_login": "2023-08-08T08:03:40.962Z", "source": null, "login_failed": 1, "out_of_office": false, "out_of_office_start_at": null, "out_of_office_end_at": null, "out_of_office_replacement_id": null, "preferences": { "locale": "en-us", "notification_config": { "matrix": { "create": { "criteria": { "owned_by_me": true, "owned_by_nobody": true, "subscribed": true, "no": false }, "channel": { "email": true, "online": true } }, "update": { "criteria": { "owned_by_me": true, "owned_by_nobody": true, "subscribed": true, "no": false }, "channel": { "email": true, "online": true } }, "reminder_reached": { "criteria": { "owned_by_me": true, "owned_by_nobody": false, "subscribed": false, "no": false }, "channel": { "email": true, "online": true } }, "escalation": { "criteria": { "owned_by_me": true, "owned_by_nobody": false, "subscribed": false, "no": false }, "channel": { "email": true, "online": true } } } }, "intro": true, "theme": "light" }, "updated_by_id": 3, "created_by_id": 1, "created_at": "2023-07-26T08:44:48.807Z", "updated_at": "2023-08-08T08:51:50.662Z", "role_ids": [ 1, 2 ], "two_factor_preference_ids": [], "organization_ids": [], "authorization_ids": [], "overview_sorting_ids": [], "group_ids": { "3": [ "full" ], "1": [ "full" ], "2": [ "full" ] } }, "4": { "id": 4, "organization_id": 2, "login": "jacob@chrispresso.com", "firstname": "Jacob", "lastname": "Smith", "email": "jacob@chrispresso.com", "image": "95afc1244af5cb8b77edcd7224c5d5f8", "image_source": null, "web": "", "phone": "", "fax": "", "mobile": "", "department": null, "street": "", "zip": "", "city": "", "country": "", "address": null, "vip": false, "verified": false, "active": true, "note": "", "last_login": null, "source": null, "login_failed": 0, "out_of_office": false, "out_of_office_start_at": null, "out_of_office_end_at": null, "out_of_office_replacement_id": null, "preferences": { "locale": "en-us", "notification_config": { "matrix": { "create": { "criteria": { "owned_by_me": true, "owned_by_nobody": true, "subscribed": true, "no": false }, "channel": { "email": true, "online": true } }, "update": { "criteria": { "owned_by_me": true, "owned_by_nobody": true, "subscribed": true, "no": false }, "channel": { "email": true, "online": true } }, "reminder_reached": { "criteria": { "owned_by_me": true, "owned_by_nobody": false, "subscribed": false, "no": false }, "channel": { "email": true, "online": true } }, "escalation": { "criteria": { "owned_by_me": true, "owned_by_nobody": false, "subscribed": false, "no": false }, "channel": { "email": true, "online": true } } } } }, "updated_by_id": 1, "created_by_id": 1, "created_at": "2023-07-26T08:44:49.390Z", "updated_at": "2023-07-26T08:44:49.585Z", "role_ids": [ 1, 2 ], "two_factor_preference_ids": [], "organization_ids": [], "authorization_ids": [], "overview_sorting_ids": [], "group_ids": { "3": [ "full" ], "1": [ "full" ], "2": [ "full" ] } }, "5": { "id": 5, "organization_id": 2, "login": "emma@chrispresso.com", "firstname": "Emma", "lastname": "Taylor", "email": "emma@chrispresso.com", "image": "b64fef91c29105b4a08a2a69be08eda3", "image_source": null, "web": "", "phone": "", "fax": "", "mobile": "", "department": null, "street": "", "zip": "", "city": "", "country": "", "address": null, "vip": false, "verified": false, "active": true, "note": "", "last_login": null, "source": null, "login_failed": 0, "out_of_office": false, "out_of_office_start_at": null, "out_of_office_end_at": null, "out_of_office_replacement_id": null, "preferences": { "locale": "en-us", "notification_config": { "matrix": { "create": { "criteria": { "owned_by_me": true, "owned_by_nobody": true, "subscribed": true, "no": false }, "channel": { "email": true, "online": true } }, "update": { "criteria": { "owned_by_me": true, "owned_by_nobody": true, "subscribed": true, "no": false }, "channel": { "email": true, "online": true } }, "reminder_reached": { "criteria": { "owned_by_me": true, "owned_by_nobody": false, "subscribed": false, "no": false }, "channel": { "email": true, "online": true } }, "escalation": { "criteria": { "owned_by_me": true, "owned_by_nobody": false, "subscribed": false, "no": false }, "channel": { "email": true, "online": true } } } } }, "updated_by_id": 1, "created_by_id": 1, "created_at": "2023-07-26T08:44:49.766Z", "updated_at": "2023-07-26T08:44:49.970Z", "role_ids": [ 2 ], "two_factor_preference_ids": [], "organization_ids": [], "authorization_ids": [], "overview_sorting_ids": [], "group_ids": { "3": [ "full" ], "1": [ "full" ], "2": [ "full" ] } }, "2": { "id": 2, "organization_id": 1, "login": "nicole.braun@zammad.org", "firstname": "Nicole", "lastname": "Braun", "email": "nicole.braun@zammad.org", "image": null, "image_source": null, "web": "", "phone": "", "fax": "", "mobile": "", "department": "", "street": "", "zip": "", "city": "", "country": "", "address": "", "vip": false, "verified": false, "active": true, "note": "", "last_login": null, "source": null, "login_failed": 0, "out_of_office": false, "out_of_office_start_at": null, "out_of_office_end_at": null, "out_of_office_replacement_id": null, "preferences": { "tickets_closed": 22, "tickets_open": 1 }, "updated_by_id": 3, "created_by_id": 1, "created_at": "2023-07-26T08:44:39.646Z", "updated_at": "2023-08-04T14:37:11.400Z", "role_ids": [ 3 ], "two_factor_preference_ids": [], "organization_ids": [], "authorization_ids": [], "overview_sorting_ids": [], "group_ids": {} } }, "Role": { "1": { "id": 1, "name": "Admin", "preferences": {}, "default_at_signup": false, "active": true, "note": "To configure your system.", "updated_by_id": 1, "created_by_id": 1, "created_at": "2023-07-26T08:44:37.326Z", "updated_at": "2023-07-26T08:44:37.326Z", "permission_ids": [ 1, 43, 55, 65 ], "knowledge_base_permission_ids": [], "group_ids": {} }, "2": { "id": 2, "name": "Agent", "preferences": {}, "default_at_signup": false, "active": true, "note": "To work on Tickets.", "updated_by_id": 1, "created_by_id": 1, "created_at": "2023-07-26T08:44:37.362Z", "updated_at": "2023-07-26T08:44:37.362Z", "permission_ids": [ 43, 57, 60, 62, 66 ], "knowledge_base_permission_ids": [], "group_ids": {} }, "3": { "id": 3, "name": "Customer", "preferences": {}, "default_at_signup": true, "active": true, "note": "People who create Tickets ask for help.", "updated_by_id": 1, "created_by_id": 1, "created_at": "2023-07-26T08:44:37.379Z", "updated_at": "2023-07-28T07:22:53.613Z", "permission_ids": [ 44, 47, 48, 50, 54, 58 ], "knowledge_base_permission_ids": [], "group_ids": {} } }, "Organization": { "2": { "name": "Chrispresso Inc.", "shared": true, "domain": "", "domain_assignment": false, "active": true, "note": "Manufacturer of individual coffee products.", "vip": false, "updated_by_id": 3, "id": 2, "created_by_id": 1, "created_at": "2023-07-26T08:44:48.617Z", "updated_at": "2023-08-04T12:01:44.370Z", "member_ids": [ 3, 4, 5 ], "secondary_member_ids": [] }, "1": { "id": 1, "name": "Zammad Foundation", "shared": true, "domain": "", "domain_assignment": false, "active": true, "note": "", "updated_by_id": 1, "created_by_id": 1, "created_at": "2023-07-26T08:44:39.608Z", "updated_at": "2023-08-04T12:02:00.018Z", "vip": false, "member_ids": [ 2 ], "secondary_member_ids": [] } } } }Add¶
Required permission:
ticket.agent
oradmin
POST
-Request sent:/api/v1/links/add
{ "link_type": "normal", "link_object_target": "Ticket", "link_object_target_value": 11, "link_object_source": "Ticket", "link_object_source_number": "93010" }Note
The value for
link_object_target
has to be the ticket ID. The value for thelink_object_source_number
has to be the ticket number.Response:
# HTTP-Code 201 Created { "id": 11, "link_type_id": 1, "link_object_source_id": 1, "link_object_source_value": 10, "link_object_target_id": 1, "link_object_target_value": 11, "created_at": "2023-08-08T11:46:44.108Z", "updated_at": "2023-08-08T11:46:44.108Z" }Delete¶
Required permission:
ticket.agent
oradmin
DELETE
-Request sent:/api/v1/links/remove
{ "link_type": "normal", "link_object_source": "Ticket", "link_object_source_value": 93010, "link_object_target": "Ticket", "link_object_target_value": 11 }Response:
# HTTP-Code 201 Created { }Mentions¶
Warning
The mention endpoint depends on the group permissions and if the user you’re using is an agent. Because of this tickets may or may not be available.
List¶
Required permission:
ticket.agent
orticket.customer
GET
-Request sent:/api/v1/mentions
# HTTP-Code 200 Ok { mentions: [ { "id":2, "mentionable_type":"Ticket", "mentionable_id":1, "user_id":3, "updated_by_id":3, "created_by_id":3, "created_at":"2021-03-16T08:51:08.985Z", "updated_at":"2021-03-16T08:51:08.985Z" }, { "id":3, "mentionable_type":"Ticket", "mentionable_id":1, "user_id":4, "updated_by_id":4, "created_by_id":4, "created_at":"2021-03-16T08:51:08.986Z", "updated_at":"2021-03-16T08:51:08.986Z" }, ] }Create¶
Required permission:
ticket.agent
POST
-Request sent:/api/v1/mentions
{ "mentionable_type": "Ticket", "mentionable_id": 12, }Response:
# HTTP-Code 201 Created { "id":2, "mentionable_type":"Ticket", "mentionable_id":1, "user_id":3, "updated_by_id":3, "created_by_id":3, "created_at":"2021-03-16T08:51:08.985Z", "updated_at":"2021-03-16T08:51:08.985Z" }The mention will be created for the user of the current session.
Delete¶
Required permission:
ticket.agent
DELETE
-Request sent:/api/v1/mentions/{id}
Response:
# HTTP-Code 200 Ok { "id":2, "mentionable_type":"Ticket", "mentionable_id":1, "user_id":3, "updated_by_id":3, "created_by_id":3, "created_at":"2021-03-16T08:51:08.985Z", "updated_at":"2021-03-16T08:51:08.985Z" }Priorities¶
List¶
Required permission:
admin.object
orticket.agent
orticket.customer
GET
-Request sent:/api/v1/ticket_priorities
Response:
# HTTP-Code 200 Ok [ { "id": 1, "name": "1 low", "default_create": false, "ui_icon": "low-priority", "ui_color": "low-priority", "note": null, "active": true, "updated_by_id": 1, "created_by_id": 1, "created_at": "2021-11-03T11:51:13.559Z", "updated_at": "2021-11-03T11:51:13.572Z" }, { "id": 2, "name": "2 normal", "default_create": true, "ui_icon": null, "ui_color": null, "note": null, "active": true, "updated_by_id": 1, "created_by_id": 1, "created_at": "2021-11-03T11:51:13.570Z", "updated_at": "2021-11-03T11:51:13.570Z" }, { "id": 3, "name": "3 high", "default_create": false, "ui_icon": "important", "ui_color": "high-priority", "note": null, "active": true, "updated_by_id": 1, "created_by_id": 1, "created_at": "2021-11-03T11:51:13.579Z", "updated_at": "2021-11-03T11:51:13.579Z" } ]Show¶
Required permission:
admin.object
orticket.agent
orticket.customer
GET
-Request sent:/api/v1/ticket_priorities/{id}
Response:
# HTTP-Code 200 Ok { "id": 3, "name": "3 high", "default_create": false, "ui_icon": "important", "ui_color": "high-priority", "note": null, "active": true, "updated_by_id": 1, "created_by_id": 1, "created_at": "2021-11-03T11:51:13.579Z", "updated_at": "2021-11-03T11:51:13.579Z" }Create¶
Required permission:
admin.object
POST
-Request sent:/api/v1/ticket_priorities
{ "name": "4 disaster", "default_create": false, "ui_icon": "important", "ui_color": "high-priority", "note": "Added via API for disasterious situations." }Response:
# HTTP-Code 201 Created { "id": 4, "name": "4 disaster", "default_create": false, "ui_icon": "important", "ui_color": "high-priority", "note": "Added via API for disasterious situations.", "active": true, "updated_by_id": 3, "created_by_id": 3, "created_at": "2021-11-08T15:31:57.704Z", "updated_at": "2021-11-08T15:31:57.704Z" }Update¶
Required permission:
admin.object
PUT
-Request sent:/api/v1/ticket_priorities/{id}
{ "ui_icon": "", "ui_color": "", "note": "Adjusted via API - not so important" }Response:
# HTTP-Code 200 Ok { "id": 3, "ui_icon": "", "ui_color": "", "note": "Adjusted via API - not so important", "updated_by_id": 3, "name": "3 high", "default_create": false, "active": true, "created_by_id": 1, "created_at": "2021-11-03T11:51:13.579Z", "updated_at": "2021-11-08T15:33:12.181Z" }Delete¶
Required permission:
admin.object
Danger
⚠ This is a permanent removal
Please note that removing priorities cannot be undone.
Removing ticket priorities with references in tickets is not possible via API - this will be indicated by
"error": "Can't delete, object has references."
. This is not a bug.Consider either setting said priority to
active: false
or adjust all tickets with the to remove priority to another priority.
DELETE
-Request sent:/api/v1/ticket_priorities/{id}
Response:
# HTTP-Code 200 Ok {}States¶
Warning
Creating, changing or removing states via below endpoints is not recommended! You can do this in UI, please have a look here.
List¶
Required permission:
admin.object
orticket.agent
orticket.customer
GET
-Request sent:/api/v1/ticket_states
Response:
# HTTP-Code 200 Ok [ { "id": 1, "state_type_id": 1, "name": "new", "next_state_id": null, "ignore_escalation": false, "default_create": true, "default_follow_up": false, "note": null, "active": true, "updated_by_id": 1, "created_by_id": 1, "created_at": "2021-11-03T11:51:13.504Z", "updated_at": "2021-11-03T11:51:13.520Z" }, { "id": 2, "state_type_id": 2, "name": "open", "next_state_id": null, "ignore_escalation": false, "default_create": false, "default_follow_up": true, "note": null, "active": true, "updated_by_id": 1, "created_by_id": 1, "created_at": "2021-11-03T11:51:13.518Z", "updated_at": "2021-11-03T11:51:13.518Z" }, { "id": 3, "state_type_id": 3, "name": "pending reminder", "next_state_id": null, "ignore_escalation": true, "default_create": false, "default_follow_up": false, "note": null, "active": true, "updated_by_id": 1, "created_by_id": 1, "created_at": "2021-11-03T11:51:13.528Z", "updated_at": "2021-11-03T11:51:13.528Z" }, { "id": 4, "state_type_id": 5, "name": "closed", "next_state_id": null, "ignore_escalation": true, "default_create": false, "default_follow_up": false, "note": null, "active": true, "updated_by_id": 1, "created_by_id": 1, "created_at": "2021-11-03T11:51:13.535Z", "updated_at": "2021-11-03T11:51:13.535Z" }, { "id": 5, "state_type_id": 6, "name": "merged", "next_state_id": null, "ignore_escalation": true, "default_create": false, "default_follow_up": false, "note": null, "active": true, "updated_by_id": 1, "created_by_id": 1, "created_at": "2021-11-03T11:51:13.540Z", "updated_at": "2021-11-03T11:51:13.540Z" }, { "id": 6, "state_type_id": 7, "name": "removed", "next_state_id": null, "ignore_escalation": true, "default_create": false, "default_follow_up": false, "note": null, "active": false, "updated_by_id": 1, "created_by_id": 1, "created_at": "2021-11-03T11:51:13.546Z", "updated_at": "2021-11-03T11:51:13.546Z" }, { "id": 7, "state_type_id": 4, "name": "pending close", "next_state_id": 4, "ignore_escalation": true, "default_create": false, "default_follow_up": false, "note": null, "active": true, "updated_by_id": 1, "created_by_id": 1, "created_at": "2021-11-03T11:51:13.553Z", "updated_at": "2021-11-03T11:51:13.553Z" } ]Show¶
Required permission:
admin.object
orticket.agent
orticket.customer
GET
-Request sent:/api/v1/ticket_states/{id}
Response:
# HTTP-Code 200 Ok { "id": 4, "state_type_id": 5, "name": "closed", "next_state_id": null, "ignore_escalation": true, "default_create": false, "default_follow_up": false, "note": null, "active": true, "updated_by_id": 1, "created_by_id": 1, "created_at": "2021-11-03T11:51:13.535Z", "updated_at": "2021-11-03T11:51:13.535Z" }Create¶
Required permission:
admin.object
Note
Below payload makes use of
state_type_id
which is a instance specific set of IDs. State types indicate how the state will work.As there’s no endpoint for retreiving these, please use the rails console.
POST
-Request sent:/api/v1/ticket_states
{ "name": "in progress", "state_type_id": 2, "ignore_escalation": true, "active": true }Response:
# HTTP-Code 201 Created { "id": 8, "state_type_id": 2, "name": "in progress", "next_state_id": null, "ignore_escalation": true, "default_create": false, "default_follow_up": false, "note": null, "active": true, "updated_by_id": 3, "created_by_id": 3, "created_at": "2021-11-08T15:08:21.671Z", "updated_at": "2021-11-08T15:08:21.671Z" }Update¶
Required permission:
admin.object
PUT
-Request sent:/api/v1/ticket_states/{id}
{ "note": "State created & updated via API" }Response:
# HTTP-Code 200 Ok { "id": 8, "note": "State created & updated via API", "updated_by_id": 3, "name": "in progress", "state_type_id": 2, "next_state_id": null, "ignore_escalation": true, "default_create": false, "default_follow_up": false, "active": true, "created_by_id": 3, "created_at": "2021-11-08T15:08:21.671Z", "updated_at": "2021-11-08T15:13:32.370Z" }Delete¶
Required permission:
admin.object
Danger
⚠ This is a permanent removal
Please note that removing ticket states cannot be undone.
Removing ticket states with references in tickets is not possible via API - this will be indicated by
"error": "Can't delete, object has references."
. This is not a bug.Consider either setting said state to
active: false
or adjust all tickets with the to remove state to another state.
DELETE
-Request sent:/api/v1/ticket_states/{id}
Response:
# HTTP-Code 200 Ok {}Tags¶
Ticket scope¶
List¶
Required permission:
ticket.agent
oradmin.tag
GET
-Request sent:/api/v1/tags?object=Ticket&o_id={ticket id}
Sample response:
# HTTP-Code 200 OK { "tags": [ "americano", "complaint" ] }Search¶
Required permission:
ticket.agent
oradmin.tag
GET
-Request sent:/api/v1/tag_search?term={tag name}
Hint
Zammad will return all tags that contain your search phrase.
Sample response:
# HTTP-Code 200 OK [ { "id": 1, "value": "americano" }, { "id": 2, "value": "complaint" }, { "id": 3, "value": "viennese melange" } ]Add¶
Required permission:
ticket.agent
oradmin.tag
POST
-Request sent:/api/v1/tags/add
{ "item": "{tag name}", "object": "Ticket", "o_id": {ticket id} }Hint
This will create the tag if it doesn’t exist and the user has permission to do so.
Response:
# HTTP-Code 201 Created trueRemove¶
Required permission:
ticket.agent
oradmin.tag
DELETE
-Request sent:/api/v1/tags/remove
{ "item": "{tag name}", "object": "Ticket", "o_id": "{ticket id}" }Response:
# HTTP-Code 201 Created trueAdministration scope¶
Admin - List¶
Required permission:
admin.tag
GET
-Request sent:/api/v1/tag_list
Sample response:
# HTTP-Code 200 OK [ { "id": 1, "name": "americano", "count": 0 }, { "id": 2, "name": "complaint", "count": 0 }, { "id": 3, "name": "viennese melange", "count": 0 } ]Admin - Create¶
Required permission:
admin.tag
POST
-Request sent:/api/v1/tag_list
{ "name": "tag 5" }Response:
# HTTP-Code 200 OK {}Admin - Rename¶
Required permission:
admin.tag
PUT
-Request sent:/api/v1/tag_list/{tag id}
{ "name": "order" }Response:
# HTTP-Code 200 OK {}Admin - Delete¶
Required permission:
admin.tag
DELETE
-Request sent:/api/v1/tag_list/{tag id}
Response:
# HTTP-Code 200 OK {}Tickets¶
Warning
Ticket endpoints depend on group permissions and if the user you’re using is an agent. Because of this tickets may or may not be available.
List¶
Required permission:
ticket.agent
orticket.customer
GET
-Request sent:/api/v1/tickets
Response:
# HTTP-Code 200 Ok [ { "id": 1, "group_id": 1, "priority_id": 2, "state_id": 1, "organization_id": 1, "number": "22001", "title": "Welcome to Zammad!", "owner_id": 1, "customer_id": 2, "note": null, "first_response_at": null, "first_response_escalation_at": null, "first_response_in_min": null, "first_response_diff_in_min": null, "close_at": null, "close_escalation_at": null, "close_in_min": null, "close_diff_in_min": null, "update_escalation_at": null, "update_in_min": null, "update_diff_in_min": null, "last_contact_at": "2021-11-03T11:51:13.790Z", "last_contact_agent_at": null, "last_contact_customer_at": "2021-11-03T11:51:13.790Z", "last_owner_update_at": null, "create_article_type_id": 5, "create_article_sender_id": 2, "article_count": 1, "escalation_at": null, "pending_time": null, "type": null, "time_unit": null, "preferences": {}, "updated_by_id": 2, "created_by_id": 2, "created_at": "2021-11-03T11:51:13.759Z", "updated_at": "2021-11-03T11:51:13.809Z" }, { "id": 2, "group_id": 1, "priority_id": 2, "state_id": 4, "organization_id": 3, "number": "22002", "title": "Order 777555", "owner_id": 3, "customer_id": 6, "note": null, "first_response_at": null, "first_response_escalation_at": null, "first_response_in_min": null, "first_response_diff_in_min": null, "close_at": null, "close_escalation_at": null, "close_in_min": null, "close_diff_in_min": null, "update_escalation_at": null, "update_in_min": null, "update_diff_in_min": null, "last_contact_at": "2021-05-04T16:57:17.920Z", "last_contact_agent_at": "2021-05-03T10:57:17.904Z", "last_contact_customer_at": "2021-05-04T16:57:17.920Z", "last_owner_update_at": null, "create_article_type_id": 1, "create_article_sender_id": 2, "article_count": 3, "escalation_at": null, "pending_time": null, "type": null, "time_unit": null, "preferences": {}, "updated_by_id": 6, "created_by_id": 6, "created_at": "2021-05-03T09:57:17.837Z", "updated_at": "2021-11-03T11:57:17.927Z" }, ... ]Search¶
Required permission:
ticket.agent
orticket.customer
GET
-Request sent:/api/v1/tickets/search?query={search string}&limit=10
Response:
# HTTP-Code 200 Ok { "tickets": [ 9, 10, 11 ], "tickets_count": 3, "assets": { "Ticket": { "9": { "id": 9, "group_id": 1, "priority_id": 3, "state_id": 2, "organization_id": 7, "number": "22009", "title": "Need more information!", "owner_id": 5, "customer_id": 10, "note": null, "first_response_at": null, "first_response_escalation_at": null, "first_response_in_min": null, "first_response_diff_in_min": null, "close_at": null, "close_escalation_at": null, "close_in_min": null, "close_diff_in_min": null, "update_escalation_at": null, "update_in_min": null, "update_diff_in_min": null, "last_contact_at": "2021-11-03T05:42:19.141Z", "last_contact_agent_at": "2021-11-03T05:42:19.141Z", "last_contact_customer_at": "2021-11-03T02:57:19.141Z", "last_owner_update_at": null, "create_article_type_id": 1, "create_article_sender_id": 2, "article_count": 4, "escalation_at": null, "pending_time": null, "type": null, "time_unit": null, "preferences": {}, "updated_by_id": 3, "created_by_id": 10, "created_at": "2021-11-03T02:57:19.141Z", "updated_at": "2021-11-03T17:48:52.849Z", "article_ids": [ 19, 18, 17, 16 ], "ticket_time_accounting_ids": [] }, "10": { "id": 10, "group_id": 1, "priority_id": 3, "state_id": 1, "organization_id": 7, "number": "22010", "title": "Heads up 🕹!", "owner_id": 1, "customer_id": 11, "note": null, "first_response_at": null, "first_response_escalation_at": null, "first_response_in_min": null, "first_response_diff_in_min": null, "close_at": null, "close_escalation_at": null, "close_in_min": null, "close_diff_in_min": null, "update_escalation_at": null, "update_in_min": null, "update_diff_in_min": null, "last_contact_at": "2021-11-03T11:57:19.227Z", "last_contact_agent_at": null, "last_contact_customer_at": "2021-11-03T11:57:19.227Z", "last_owner_update_at": null, "create_article_type_id": 1, "create_article_sender_id": 2, "article_count": 1, "escalation_at": null, "pending_time": null, "type": null, "time_unit": null, "preferences": {}, "updated_by_id": 3, "created_by_id": 11, "created_at": "2021-11-03T02:57:19.216Z", "updated_at": "2021-11-03T17:48:52.730Z", "article_ids": [ 20 ], "ticket_time_accounting_ids": [] }, "11": { "id": 11, "group_id": 1, "priority_id": 3, "state_id": 1, "organization_id": 3, "number": "22011", "title": "Surprise - well done", "owner_id": 1, "customer_id": 6, "note": null, "first_response_at": null, "first_response_escalation_at": null, "first_response_in_min": null, "first_response_diff_in_min": null, "close_at": null, "close_escalation_at": null, "close_in_min": null, "close_diff_in_min": null, "update_escalation_at": null, "update_in_min": null, "update_diff_in_min": null, "last_contact_at": "2021-11-03T02:57:19.243Z", "last_contact_agent_at": null, "last_contact_customer_at": "2021-11-03T02:57:19.243Z", "last_owner_update_at": null, "create_article_type_id": 11, "create_article_sender_id": 2, "article_count": 1, "escalation_at": null, "pending_time": null, "type": null, "time_unit": null, "preferences": {}, "updated_by_id": 6, "created_by_id": 6, "created_at": "2021-11-03T02:57:19.243Z", "updated_at": "2021-11-03T11:57:19.263Z", "article_ids": [ 21 ], "ticket_time_accounting_ids": [] } }, "User": { "10": { "id": 10, "organization_id": null, "login": "david@example.com", "firstname": "David", "lastname": "Bell", "email": "david@example.com", "image": "d829d234f377f231534802df6d5500a7", "image_source": null, "web": "", "phone": "0033 892 12 34 56", "fax": "", "mobile": "", "department": "", "street": "", "zip": "", "city": "", "country": "", "address": "Eiffel Tower\r\n5 Avenue Anatole France\r\n75007 Paris", "vip": false, "verified": false, "active": true, "note": "did order viennese melange, ask next time if the flavor was as expected", "last_login": null, "source": null, "login_failed": 0, "out_of_office": false, "out_of_office_start_at": null, "out_of_office_end_at": null, "out_of_office_replacement_id": null, "preferences": { "tickets_closed": 1, "tickets_open": 3, "mail_delivery_failed": true, "mail_delivery_failed_data": "2021-11-08T13:38:32.059Z" }, "updated_by_id": 1, "created_by_id": 1, "created_at": "2021-11-03T11:57:17.495Z", "updated_at": "2021-11-08T13:45:04.107Z", "role_ids": [ 3 ], "organization_ids": [], "authorization_ids": [], "karma_user_ids": [], "group_ids": {}, "accounts": {} }, "1": { "id": 1, "organization_id": null, "login": "-", "firstname": "-", "lastname": "", "email": "", "image": null, "image_source": null, "web": "", "phone": "", "fax": "", "mobile": "", "department": "", "street": "", "zip": "", "city": "", "country": "", "address": "", "vip": false, "verified": false, "active": false, "note": "", "last_login": null, "source": null, "login_failed": 0, "out_of_office": false, "out_of_office_start_at": null, "out_of_office_end_at": null, "out_of_office_replacement_id": null, "preferences": {}, "updated_by_id": 1, "created_by_id": 1, "created_at": "2021-11-03T11:51:12.786Z", "updated_at": "2021-11-03T11:51:12.786Z", "role_ids": [], "organization_ids": [], "authorization_ids": [], "karma_user_ids": [], "group_ids": {}, "accounts": {} }, "3": { "id": 3, "organization_id": 2, "login": "chris@chrispresso.com", "firstname": "Christopher", "lastname": "Miller", "email": "chris@chrispresso.com", "image": "7a6a0d1d94ad2037153cf3a6c1b49a53", "image_source": null, "web": "", "phone": "", "fax": "", "mobile": "", "department": "", "street": "", "zip": "", "city": "", "country": "", "address": "", "vip": false, "verified": false, "active": true, "note": "", "last_login": "2021-11-03T12:26:53.410Z", "source": null, "login_failed": 0, "out_of_office": false, "out_of_office_start_at": null, "out_of_office_end_at": null, "out_of_office_replacement_id": null, "preferences": { "notification_config": { "matrix": { "create": { "criteria": { "owned_by_me": true, "owned_by_nobody": true, "subscribed": true, "no": false }, "channel": { "email": true, "online": true } }, "update": { "criteria": { "owned_by_me": true, "owned_by_nobody": true, "subscribed": true, "no": false }, "channel": { "email": true, "online": true } }, "reminder_reached": { "criteria": { "owned_by_me": true, "owned_by_nobody": false, "subscribed": false, "no": false }, "channel": { "email": true, "online": true } }, "escalation": { "criteria": { "owned_by_me": true, "owned_by_nobody": false, "subscribed": false, "no": false }, "channel": { "email": true, "online": true } } } }, "locale": "en-us", "intro": true, "chat": { "active": { "1": "on" } } }, "updated_by_id": 3, "created_by_id": 1, "created_at": "2021-11-03T11:57:15.975Z", "updated_at": "2021-11-08T13:45:07.798Z", "role_ids": [ 1, 2 ], "organization_ids": [], "authorization_ids": [], "karma_user_ids": [ 1 ], "group_ids": { "1": [ "full" ], "2": [ "full" ], "3": [ "full" ] }, "accounts": {} }, "4": { "id": 4, "organization_id": 2, "login": "jacob@chrispresso.com", "firstname": "Jacob", "lastname": "Smith", "email": "jacob@chrispresso.com", "image": "95afc1244af5cb8b77edcd7224c5d5f8", "image_source": null, "web": "", "phone": "", "fax": "", "mobile": "", "department": null, "street": "", "zip": "", "city": "", "country": "", "address": null, "vip": false, "verified": false, "active": true, "note": "", "last_login": null, "source": null, "login_failed": 0, "out_of_office": false, "out_of_office_start_at": null, "out_of_office_end_at": null, "out_of_office_replacement_id": null, "preferences": { "notification_config": { "matrix": { "create": { "criteria": { "owned_by_me": true, "owned_by_nobody": true, "subscribed": true, "no": false }, "channel": { "email": true, "online": true } }, "update": { "criteria": { "owned_by_me": true, "owned_by_nobody": true, "subscribed": true, "no": false }, "channel": { "email": true, "online": true } }, "reminder_reached": { "criteria": { "owned_by_me": true, "owned_by_nobody": false, "subscribed": false, "no": false }, "channel": { "email": true, "online": true } }, "escalation": { "criteria": { "owned_by_me": true, "owned_by_nobody": false, "subscribed": false, "no": false }, "channel": { "email": true, "online": true } } } }, "locale": "en-us" }, "updated_by_id": 1, "created_by_id": 1, "created_at": "2021-11-03T11:57:16.160Z", "updated_at": "2021-11-03T11:57:16.214Z", "role_ids": [ 1, 2 ], "organization_ids": [], "authorization_ids": [], "karma_user_ids": [], "group_ids": { "1": [ "full" ], "2": [ "full" ], "3": [ "full" ] }, "accounts": {} }, "5": { "id": 5, "organization_id": 7, "login": "emma@chrispresso.com", "firstname": "Emma", "lastname": "Taylor", "email": "emma@chrispresso.com", "image": "b64fef91c29105b4a08a2a69be08eda3", "image_source": null, "web": "", "phone": "", "fax": "", "mobile": "", "department": null, "street": "", "zip": "", "city": "", "country": "", "address": null, "vip": false, "verified": false, "active": true, "note": "", "last_login": null, "source": null, "login_failed": 0, "out_of_office": false, "out_of_office_start_at": null, "out_of_office_end_at": null, "out_of_office_replacement_id": null, "preferences": { "notification_config": { "matrix": { "create": { "criteria": { "owned_by_me": true, "owned_by_nobody": true, "subscribed": true, "no": false }, "channel": { "email": true, "online": true } }, "update": { "criteria": { "owned_by_me": true, "owned_by_nobody": true, "subscribed": true, "no": false }, "channel": { "email": true, "online": true } }, "reminder_reached": { "criteria": { "owned_by_me": true, "owned_by_nobody": false, "subscribed": false, "no": false }, "channel": { "email": true, "online": true } }, "escalation": { "criteria": { "owned_by_me": true, "owned_by_nobody": false, "subscribed": false, "no": false }, "channel": { "email": true, "online": true } } } }, "locale": "en-us" }, "updated_by_id": 3, "created_by_id": 1, "created_at": "2021-11-03T11:57:16.349Z", "updated_at": "2021-11-08T13:22:38.130Z", "role_ids": [ 2 ], "organization_ids": [], "authorization_ids": [], "karma_user_ids": [], "group_ids": { "1": [ "full" ], "2": [ "full" ], "3": [ "full" ] }, "accounts": {} }, "11": { "id": 11, "organization_id": 7, "login": "olivia@example.com", "firstname": "Olivia", "lastname": "Ross", "email": "olivia@example.com", "image": "b6f7a2d56544bb471eb3a3c238c7d964", "image_source": null, "web": "", "phone": "0044 20 1234 5678", "fax": "", "mobile": "", "department": "", "street": "", "zip": "", "city": "", "country": "", "address": "Westminster\r\nLondon SW1A 0AA", "vip": false, "verified": false, "active": true, "note": "", "last_login": null, "source": null, "login_failed": 0, "out_of_office": false, "out_of_office_start_at": null, "out_of_office_end_at": null, "out_of_office_replacement_id": null, "preferences": { "tickets_closed": 0, "tickets_open": 1 }, "updated_by_id": 3, "created_by_id": 1, "created_at": "2021-11-03T11:57:17.741Z", "updated_at": "2021-11-03T17:48:52.739Z", "role_ids": [ 3 ], "organization_ids": [], "authorization_ids": [], "karma_user_ids": [], "group_ids": {}, "accounts": {} }, "16": { "id": 16, "organization_id": 7, "login": "jdoe", "firstname": "Jane", "lastname": "Doe", "email": "jdoe@example.com", "image": null, "image_source": null, "web": "", "phone": "+49 30 55 57 160 00", "fax": "", "mobile": "", "department": "Sales", "street": "", "zip": "", "city": "", "country": "", "address": "Marienstr. 18\r\n10117 Berlin", "vip": false, "verified": false, "active": true, "note": "", "last_login": null, "source": null, "login_failed": 0, "out_of_office": false, "out_of_office_start_at": null, "out_of_office_end_at": null, "out_of_office_replacement_id": null, "preferences": { "notification_config": { "matrix": { "create": { "criteria": { "owned_by_me": true, "owned_by_nobody": true, "subscribed": true, "no": false }, "channel": { "email": true, "online": true } }, "update": { "criteria": { "owned_by_me": true, "owned_by_nobody": true, "subscribed": true, "no": false }, "channel": { "email": true, "online": true } }, "reminder_reached": { "criteria": { "owned_by_me": true, "owned_by_nobody": false, "subscribed": false, "no": false }, "channel": { "email": true, "online": true } }, "escalation": { "criteria": { "owned_by_me": true, "owned_by_nobody": false, "subscribed": false, "no": false }, "channel": { "email": true, "online": true } } } }, "locale": "en-us" }, "updated_by_id": 3, "created_by_id": 3, "created_at": "2021-11-03T14:42:36.855Z", "updated_at": "2021-11-08T13:20:18.500Z", "role_ids": [ 2, 3 ], "organization_ids": [], "authorization_ids": [], "karma_user_ids": [], "group_ids": {}, "accounts": {} }, "6": { "id": 6, "organization_id": 3, "login": "anna@example.com", "firstname": "Anna", "lastname": "Lopez", "email": "anna@example.com", "image": "4b1cb1fae2e608ffa72099774e1f57ad", "image_source": null, "web": "", "phone": "415-123-5858", "fax": "", "mobile": "", "department": null, "street": "", "zip": "", "city": "", "country": "", "address": "Golden Gate Bridge\nSan Francisco, CA 94129", "vip": false, "verified": false, "active": true, "note": "likes espresso romano - recommended espresso con panna", "last_login": null, "source": null, "login_failed": 0, "out_of_office": false, "out_of_office_start_at": null, "out_of_office_end_at": null, "out_of_office_replacement_id": null, "preferences": {}, "updated_by_id": 1, "created_by_id": 1, "created_at": "2021-11-03T11:57:16.526Z", "updated_at": "2021-11-03T11:57:16.611Z", "role_ids": [ 3 ], "organization_ids": [], "authorization_ids": [], "karma_user_ids": [], "group_ids": {}, "accounts": {} }, "7": { "id": 7, "organization_id": 3, "login": "samuel@example.com", "firstname": "Samuel", "lastname": "Lee", "email": "samuel@example.com", "image": "5911d228f3588c36a72d80eb0c1e4d08", "image_source": null, "web": "", "phone": "855-666-7777", "fax": "", "mobile": "", "department": null, "street": "", "zip": "", "city": "", "country": "", "address": "5201 Blue Lagoon Drive\n8th Floor & 9th Floor\nMiami, FL 33126", "vip": false, "verified": false, "active": true, "note": "likes americano, did order two units", "last_login": null, "source": null, "login_failed": 0, "out_of_office": false, "out_of_office_start_at": null, "out_of_office_end_at": null, "out_of_office_replacement_id": null, "preferences": {}, "updated_by_id": 1, "created_by_id": 1, "created_at": "2021-11-03T11:57:16.748Z", "updated_at": "2021-11-03T11:57:16.861Z", "role_ids": [ 3 ], "organization_ids": [], "authorization_ids": [], "karma_user_ids": [], "group_ids": {}, "accounts": {} }, "8": { "id": 8, "organization_id": 3, "login": "emily@example.com", "firstname": "Emily", "lastname": "Adams", "email": "emily@example.com", "image": "99ba64a89f7783c099c304c9b00ff9e8", "image_source": null, "web": "", "phone": "0061 2 1234 7777", "fax": "", "mobile": "", "department": null, "street": "", "zip": "", "city": "", "country": "", "address": "Bennelong Point\nSydney NSW 2000", "vip": false, "verified": false, "active": true, "note": "did order café au lait, ask next time if the flavor was as expected", "last_login": null, "source": null, "login_failed": 0, "out_of_office": false, "out_of_office_start_at": null, "out_of_office_end_at": null, "out_of_office_replacement_id": null, "preferences": {}, "updated_by_id": 1, "created_by_id": 1, "created_at": "2021-11-03T11:57:17.000Z", "updated_at": "2021-11-03T11:57:17.060Z", "role_ids": [ 3 ], "organization_ids": [], "authorization_ids": [], "karma_user_ids": [], "group_ids": {}, "accounts": {} } }, "Role": { "3": { "id": 3, "name": "Customer", "preferences": {}, "default_at_signup": true, "active": true, "note": "People who create Tickets ask for help.", "updated_by_id": 1, "created_by_id": 1, "created_at": "2021-11-03T11:51:12.856Z", "updated_at": "2021-11-08T13:38:31.573Z", "permission_ids": [ 42, 45, 46, 48, 54 ], "group_ids": {} }, "1": { "id": 1, "name": "Admin", "preferences": {}, "default_at_signup": false, "active": true, "note": "To configure your system.", "updated_by_id": 1, "created_by_id": 1, "created_at": "2021-11-03T11:51:12.831Z", "updated_at": "2021-11-03T11:51:12.831Z", "permission_ids": [ 1, 41, 51, 61 ], "group_ids": {} }, "2": { "id": 2, "name": "Agent", "preferences": {}, "default_at_signup": false, "active": true, "note": "To work on Tickets.", "updated_by_id": 3, "created_by_id": 1, "created_at": "2021-11-03T11:51:12.848Z", "updated_at": "2021-11-03T14:42:36.875Z", "permission_ids": [ 41, 53, 56, 58, 62 ], "group_ids": {} } }, "Group": { "1": { "id": 1, "signature_id": 1, "email_address_id": null, "name": "Sales", "assignment_timeout": null, "follow_up_possible": "yes", "follow_up_assignment": true, "active": true, "note": "Standard Group/Pool for Tickets.", "updated_by_id": 1, "created_by_id": 1, "created_at": "2021-11-03T11:51:13.449Z", "updated_at": "2021-11-08T13:37:57.093Z", "user_ids": [ 4, 5, 3 ] }, "2": { "id": 2, "signature_id": null, "email_address_id": null, "name": "2nd Level", "assignment_timeout": null, "follow_up_possible": "yes", "follow_up_assignment": true, "active": true, "note": null, "updated_by_id": 1, "created_by_id": 1, "created_at": "2021-11-03T11:57:15.802Z", "updated_at": "2021-11-08T13:37:57.097Z", "user_ids": [ 4, 5, 3 ] }, "3": { "id": 3, "signature_id": null, "email_address_id": null, "name": "Service Desk", "assignment_timeout": null, "follow_up_possible": "yes", "follow_up_assignment": true, "active": true, "note": null, "updated_by_id": 1, "created_by_id": 1, "created_at": "2021-11-03T11:57:15.807Z", "updated_at": "2021-11-08T13:37:57.102Z", "user_ids": [ 4, 5, 3 ] } }, "Organization": { "2": { "id": 2, "name": "Chrispresso Inc.", "shared": true, "domain": "", "domain_assignment": false, "active": true, "note": "Manufacturer of individual coffee products.", "updated_by_id": 1, "created_by_id": 1, "created_at": "2021-11-03T11:57:15.817Z", "updated_at": "2021-11-08T13:22:38.145Z", "member_ids": [ 3, 4 ] }, "7": { "id": 7, "name": "Sample Corp.", "shared": false, "domain": "", "domain_assignment": false, "active": true, "note": "This was a triump - I'm making a note here - H-U-G-E success!", "updated_by_id": 3, "created_by_id": 3, "created_at": "2021-11-03T17:48:52.613Z", "updated_at": "2021-11-08T13:22:38.148Z", "member_ids": [ 5, 11, 16 ] }, "3": { "id": 3, "name": "Awesome Customer Inc.", "shared": true, "domain": "", "domain_assignment": false, "active": true, "note": "Global distributor of communication and security products, electrical and electronic wire & cable.", "updated_by_id": 1, "created_by_id": 1, "created_at": "2021-11-03T11:57:15.825Z", "updated_at": "2021-11-03T11:57:15.825Z", "member_ids": [ 6, 7, 8 ] } } } }Warning
tickets_count
returns the current number of returned tickets, not the total amount.Show¶
Required permission:
ticket.agent
orticket.customer
GET
-Request sent:/api/v1/tickets/{ticket id}
Response:
# HTTP-Code 200 Ok { "id": 3, "group_id": 1, "priority_id": 2, "state_id": 4, "organization_id": 3, "number": "22003", "title": "Order 787556", "owner_id": 3, "customer_id": 7, "note": null, "first_response_at": null, "first_response_escalation_at": null, "first_response_in_min": null, "first_response_diff_in_min": null, "close_at": null, "close_escalation_at": null, "close_in_min": null, "close_diff_in_min": null, "update_escalation_at": null, "update_in_min": null, "update_diff_in_min": null, "last_contact_at": "2021-06-03T09:57:17.987Z", "last_contact_agent_at": "2021-06-03T09:57:17.987Z", "last_contact_customer_at": "2021-06-01T11:57:17.935Z", "last_owner_update_at": null, "create_article_type_id": 1, "create_article_sender_id": 2, "article_count": 2, "escalation_at": null, "pending_time": null, "type": null, "time_unit": null, "preferences": {}, "updated_by_id": 4, "created_by_id": 7, "created_at": "2021-06-01T11:57:17.935Z", "updated_at": "2021-11-03T11:57:17.997Z" }Create¶
Required permission:
ticket.agent
orticket.customer
Tip
🐱👤 On behalf of users
If you want to create tickets on behalf of other users, use the
customer_id
attribute.ticket.agent
is mandatory for this. Useguess:{email address}
to save an API call if you don’t know the user’s ID or want to create the user in question ("customer_id": "guess:jane@doe.com"
).📣 Add mention subscription right away
Add the
mentions
attribute to your ticket payload and provide an array of user ids to directly subscribe them during ticket creation.E.g.:
"mentions": [1, 5, 7, 8],
POST
-Request sent:/api/v1/tickets
{ "title": "Help me!", "group": "2nd Level", "customer": "david@example.com", "article": { "subject": "My subject", "body": "I am a message!", "type": "note", "internal": false } }Response:
# HTTP-Code 201 Created { "id": 19, "group_id": 2, "priority_id": 2, "state_id": 1, "organization_id": null, "number": "22019", "title": "Help me!", "owner_id": 1, "customer_id": 10, "note": null, "first_response_at": null, "first_response_escalation_at": null, "first_response_in_min": null, "first_response_diff_in_min": null, "close_at": null, "close_escalation_at": null, "close_in_min": null, "close_diff_in_min": null, "update_escalation_at": null, "update_in_min": null, "update_diff_in_min": null, "last_contact_at": null, "last_contact_agent_at": null, "last_contact_customer_at": null, "last_owner_update_at": null, "create_article_type_id": 10, "create_article_sender_id": 1, "article_count": 1, "escalation_at": null, "pending_time": null, "type": null, "time_unit": null, "preferences": {}, "updated_by_id": 3, "created_by_id": 3, "created_at": "2021-11-08T14:17:41.913Z", "updated_at": "2021-11-08T14:17:41.994Z", "article_ids": [ 30 ], "ticket_time_accounting_ids": [] }Hint
For more article attributes and options have a look into Articles.
Update¶
Required permission:
ticket.agent
orticket.customer
PUT
-Request sent:/api/v1/tickets/{ticket id}
{ "title": "No help for you", "group": "Sales", "state": "open", "priority": "3 high", "article": { "subject": "Update via API", "body": "Here's my reason for updating this ticket...", "internal": true } }Note
Above example provides an article. This article is a new article and does not affect any existing ones.
Response:
# HTTP-Code 200 Ok { "id": 19, "group_id": 1, "priority_id": 3, "state_id": 2, "organization_id": null, "number": "22019", "title": "No help for you", "owner_id": 1, "customer_id": 10, "note": null, "first_response_at": null, "first_response_escalation_at": null, "first_response_in_min": null, "first_response_diff_in_min": null, "close_at": null, "close_escalation_at": null, "close_in_min": null, "close_diff_in_min": null, "update_escalation_at": null, "update_in_min": null, "update_diff_in_min": null, "last_contact_at": null, "last_contact_agent_at": null, "last_contact_customer_at": null, "last_owner_update_at": null, "create_article_type_id": 10, "create_article_sender_id": 1, "article_count": 2, "escalation_at": null, "pending_time": null, "type": null, "time_unit": null, "preferences": {}, "updated_by_id": 3, "created_by_id": 3, "created_at": "2021-11-08T14:17:41.913Z", "updated_at": "2021-11-08T14:18:53.426Z", "article_ids": [ 31, 30 ], "ticket_time_accounting_ids": [] }Tip
Adding attachments
Attachment payloads are identical to the
POST
method, just usePUT
instead.Delete¶
Required permission:
admin
Danger
⚠ This is a permanent removal
Please note that removing tickets cannot be undone. All data (e.g.: articles & attachments) will be lost.
DELETE
-Request sent:/api/v1/tickets/{ticket id}
Response:
# HTTP-Code 200 Ok {}Time Accounting¶
List¶
Required permission:
ticket.agent
oradmin.time_accounting
GET
-Request sent:/api/v1/tickets/{ticket id}/time_accountings
Sample response:
# HTTP-Code 200 OK [ { "id": 6, "ticket_id": 50, "ticket_article_id": 87, "time_unit": "15.0", "type_id": 3, "created_by_id": 3, "created_at": "2023-08-16T08:11:49.315Z", "updated_at": "2023-08-16T08:11:49.315Z" }, { "id": 7, "ticket_id": 50, "ticket_article_id": 88, "time_unit": "30.0", "type_id": 2, "created_by_id": 3, "created_at": "2023-08-16T08:12:02.249Z", "updated_at": "2023-08-16T08:12:02.249Z" }, { "id": 8, "ticket_id": 50, "ticket_article_id": 89, "time_unit": "35.0", "type_id": 4, "created_by_id": 3, "created_at": "2023-08-16T08:12:29.910Z", "updated_at": "2023-08-16T08:12:29.910Z" } ]Show¶
Required permission:
ticket.agent
oradmin.time_accounting
GET
-Request sent:/api/v1/tickets/{ticket id}/time_accountings/{timeaccounting id}
Sample response:
# HTTP-Code 200 OK { "id": 7, "ticket_id": 50, "ticket_article_id": 88, "time_unit": "30.0", "type_id": 2, "created_by_id": 3, "created_at": "2023-08-16T08:12:02.249Z", "updated_at": "2023-08-16T08:12:02.249Z" }Create¶
Required permission:
ticket.agent
oradmin.time_accounting
POST
-Request sent:/api/v1/tickets/{ticket id}/time_accountings
{ "time_unit": "60.0", "type_id": 4 }Response:
# HTTP-Code 201 Created { "id": 9, "ticket_id": 50, "ticket_article_id": null, "time_unit": "60.0", "type_id": 4, "created_by_id": 3, "created_at": "2023-08-16T08:30:36.138Z", "updated_at": "2023-08-16T08:30:36.138Z" }Update¶
Required permission:
admin.time_accounting
PUT
-Request sent:/api/v1/tickets/{ticket id}/time_accounting/{timeaccounting id}
{ "id": 7, "time_unit": "15.0", "type_id": 4 }Response:
# HTTP-Code 200 OK { "ticket_id": 50, "time_unit": "15.0", "type_id": 4, "id": 7, "ticket_article_id": 88, "created_by_id": 3, "created_at": "2023-08-16T08:12:02.249Z", "updated_at": "2023-08-16T08:24:00.788Z" }Remove¶
Required permission:
admin.time_accounting
DELETE
-Request sent:/api/v1/tickets/{ticket id}/time_accountings/{timeaccounting id}
Response:
# HTTP-Code 200 OK
Generic CTI¶
This page describes the generic CTI API scopes and functionalities.
Warning
🚧 Limitations / Notes🚧
Authentication on this endpoint works fundamentally different compared to the rest of the API.
API clients do not work with the CTI endpoints unless explicitly stated by the client vendor!
The CTI endpoints are relevant for PBX systems only.
- Features
Here’s a small condensed list of the possibilities this CTI API provides.
- Inbound
Caller log functions for your agents.
Blocking of CallerIDs during signaling.
- Outbound
Caller log functions for your agents.
Set outbound caller IDs depending on the caller ID target.
- Endpoint
The endpoint can be found in the generic CTI integration and contains a unique token which acts as authentication. Make sure to keep this endpoint URL safe.
Hint
Generic CTI configuration and the correct endpoint can be found in your Zammad integration settings and are documented in our admin documentation.
Please also note the there listed requirements and limitations.All options that require returns (e.g. blocking, manipulating outgoing caller IDs) rely on configurations within the Zammad CTI integration page.- Events
There are several events in terms of an ongoing call. These actions always come from your PBX system and may be:
“newCall” event (initiation of a call)
“hangup” event (call ending)
“answer” event (aka picking up the phone)
In some situations Zammad may provide a return on your PBX calls (e.g. a reject) if you blocked a specific caller. Zammad will never initiate specific actions with your PBX. Zammad is a passive component in all described cases.
New call¶
Attribute |
Possible value |
Description |
---|---|---|
|
|
Tell Zammad there’s a new call |
|
e.g. |
Number that initiated the call |
|
e.g. |
Number that is being called |
|
|
The call direction - if your agent initiates a call this will be |
|
e.g. |
An ID that is unique for the call. Zammad will use this ID to identify an existing call with following actions (e.g. like answering or hanging up) This ID must be unique per call session. |
|
e.g. |
The user(s) real name involved. You may have to provide array style
( If the direction is
out , this is the name of the calling person(s).If the direction is
in , this is the name of the called person(s).This value is optional. |
|
e.g. |
An optional queue name, this option is relevant for the Caller Log Filter |
There’s two options on how to POST
the relevant data to Zammad.
- Example:
Below calls have been sent with the following configuration. This is important for you to understand the returns we’re showing here.
Outbound:
Destination caller ID
4989*
set outbound caller ID498999998145
with note “All from munich”Destination caller ID
4930*
set outbound caller ID493023125877
“All from Berlin”
Other settings:
Default caller ID for outbound calls
496990009111
POST
-Request sent:
https://{FQDN-Zammad}/api/v1/cti/{instance specific token}
- Outbound
Payload:
{ "event": "newCall", "from": "493023125741", "to": "492214710334", "direction": "out", "callId": "f4ebd2be-7b9a-4d58-94c2-eb06a3c2ce76", "user": "Christopher Miller" }
Returns:
{ "action": "dial", "caller_id": "496990009111", "number": "492214710334" }
Sample curl command:
$ curl --request POST 'https://{FQDN-Zammad}/api/v1/cti/{instance specific token}' \ --header 'Content-Type: application/json' \ --data-raw '{ "event": "newCall", "from": "493023125741", "to": "492214710334", "direction": "out", "callId": "f4ebd2be-7b9a-4d58-94c2-eb06a3c2ce76", "user": "Christopher Miller" }'
- Inbound
Payload:
{ "event": "newCall", "from": "493023125741", "to": "492214710334", "direction": "in", "callId": "307fa962-de8d-4ffc-817b-7f6993204159", "user": ["Christopher Miller", "Emma Taylor"] }
Response:
{}
Sample curl command:
$ curl --request POST 'https://{FQDN-Zammad}/api/v1/cti/{instance specific token}' \ --header 'Content-Type: application/json' \ --data-raw '{ "event": "newCall", "from": "493023125741", "to": "492214710334", "direction": "in", "callId": "307fa962-de8d-4ffc-817b-7f6993204159", "user": ["Christopher Miller", "Emma Taylor"] }'
POST
-Request sent:
https://{FQDN-Zammad}/api/v1/cti/{instance specific token}
- Outbound
Payload:
event:"newCall" from:"493023125741" to:"492214710334" direction:"out" callId:"f0871278-0600-4f5c-a746-bec3acf04f41" user:"Christopher Miller"
Returns:
{ "action": "dial", "caller_id": "496990009111", "number": "492214710334" }
Sample curl command:
$ curl --request POST 'https://{FQDN-Zammad}/api/v1/cti/{instance specific token}' \ --form 'event="newCall"' \ --form 'from="493023125741"' \ --form 'to="492214710334"' \ --form 'direction="out"' \ --form 'callId="f0871278-0600-4f5c-a746-bec3acf04f41"' \ --form 'user="Christopher Miller"'
- Inbound
Payload:
event:"newCall" from:"493023125741" to:"492214710334" direction:"in" callId:"25641e3f-3317-4c48-80b3-fc573c7ffe2b" user[]:"Christopher Miller" user[]:"Emma Taylor"
Returns:
{}
Sample curl command:
$ curl --request POST 'https://{FQDN-Zammad}/api/v1/cti/{instance specific token}' \ --form 'event="newCall"' \ --form 'from="493023125741"' \ --form 'to="492214710334"' \ --form 'direction="in"' \ --form 'callId="25641e3f-3317-4c48-80b3-fc573c7ffe2b"' \ --form 'user[]="Christopher Miller"' \ --form 'user[]="Emma Taylor"'
Situation specific responses¶
Depending on the chosen call direction, Zammad will return either a (optionally) configured call ID or (optionally) block a caller. If your Zammad hasn’t configured one or both options, the return will be empty.
Note
This has to be supported by your PBX in order to work.
If an incoming new call matches a to block number, Zammad will return the following.
{ "action": "reject", "reason": "busy" }
If no to block number matches, Zammad will return the following.
{}
Warning
Your PBX still needs to end the call (hangup event). Other wise the call will not just appear within Zammads caller log but also appear as ringing call.
In case your instance has a matching overwriting caller ID configured, Zammad will return the following payload.
{ "action": "dial", "callerId": "493055571642", "number": "491711234567890" }
If no overwrite match is found or you haven’t configured anything, Zammad will return the following.
{}
Call hangup¶
Attribute |
Possible value |
Description |
||||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
Tell Zammad that somebody hung up the call. |
||||||||||||||||
|
e.g. |
Number that initiated the call |
||||||||||||||||
|
e.g. |
Number that is being called |
||||||||||||||||
|
|
The call direction - if your agent initiates a call this will be |
||||||||||||||||
|
e.g. |
An ID that is unique for the call. Zammad will use this ID to identify an existing call with following actions (e.g. like answering or hanging up) This ID must be unique per call session. |
||||||||||||||||
|
|
This defines the reason of the hangup. Zammad evaluates the cause and indicates e.g. missed calls accordingly in the caller log. |
||||||||||||||||
|
e.g. |
Zammad will look up for a user with given value, the following attributes will be evaluated in given order:
This value is optional. |
There’s two options on how to POST
the relevant data to Zammad.
- Example:
Below calls have been sent with the following configuration. This is important for you to understand the returns we’re showing here.
Outbound:
Destination caller ID
4989*
set outbound caller ID498999998145
with note “All from munich”Destination caller ID
4930*
set outbound caller ID493023125877
“All from Berlin”
Other settings:
Default caller ID for outbound calls
496990009111
POST
-Request send:
https://{FQDN-Zammad}/api/v1/cti/{instance specific token}
- Outbound
Payload:
{ "event": "hangup", "from": "493023125741", "to": "492214710334", "direction": "out", "callId": "f4ebd2be-7b9a-4d58-94c2-eb06a3c2ce76", "cause": "cancel" }
Response:
{}
Sample curl command:
$ curl --request POST 'https://{FQDN-Zammad}/api/v1/cti/{instance specific token}' \ --header 'Content-Type: application/json' \ --data-raw '{ "event": "hangup", "from": "493023125741", "to": "492214710334", "direction": "out", "callId": "f4ebd2be-7b9a-4d58-94c2-eb06a3c2ce76", "cause": "cancel" }'
- Inbound
Payload:
{ "event": "hangup", "from": "493023125741", "to": "492214710334", "direction": "in", "callId": "307fa962-de8d-4ffc-817b-7f6993204159", "answeringNumber": "emma@chrispresso.com", "cause": "normalClearing" }
Response:
{}
Sample curl command:
$ curl --request POST 'https://{FQDN-Zammad}/api/v1/cti/{instance specific token}' \ --header 'Content-Type: application/json' \ --data-raw '{ "event": "hangup", "from": "493023125741", "to": "492214710334", "direction": "in", "callId": "307fa962-de8d-4ffc-817b-7f6993204159", "answeringNumber": "emma@chrispresso.com", "cause": "normalClearing" }'
POST
-Request sent:
https://{FQDN-Zammad}/api/v1/cti/{instance specific token}
- Outbound
Payload:
event:"hangup" from:"493023125741" to:"492214710334" direction:"out" callId:"da7cf8b8-2de2-4120-93c8-7db1f55225dc" cause:"cancel"
Returns:
{}
Sample curl command:
$ curl --request POST 'https://{FQDN-Zammad}/api/v1/cti/{instance specific token}' \ --form 'event="hangup"' \ --form 'from="493023125741"' \ --form 'to="492214710334"' \ --form 'direction="out"' \ --form 'callId="da7cf8b8-2de2-4120-93c8-7db1f55225dc"' \ --form 'cause="cancel"'
- Inbound
Payload:
event:"hangup" from:"493023125741" to:"492214710334" direction:"in" callId:"2d77882f-68df-40f0-8c62-b642589c00bc" answeringNumber:"emma@chrispresso.com", cause:"normalClearing"
Returns:
{}
Sample curl command:
$ curl --request POST 'https://{FQDN-Zammad}/api/v1/cti/{instance specific token}' \ --form 'event="hangup"' \ --form 'from="493023125741"' \ --form 'to="492214710334"' \ --form 'direction="in"' \ --form 'callId="2d77882f-68df-40f0-8c62-b642589c00bc"' \ --form 'answeringNumber="emma@chrispresso.com"' \ --form 'cause="normalClearing"'
Call answered¶
Attribute |
Possible value |
Description |
---|---|---|
|
|
Tell Zammad that someone answered the call. |
|
e.g. |
Number that initiated the call |
|
e.g. |
Number that is being called |
|
|
The call direction - if your agent initiates a call this will be |
|
e.g. |
An ID that is unique for the call. Zammad will use this ID to identify an existing call with following actions (e.g. like answering or hanging up) This ID must be unique per call session. |
|
e.g. |
Zammad will look up for a user with given value, the following attributes will be evaluated in given order:
This value is optional. |
|
e.g. |
The user(s) real name involved. You may have to provide array style
( If the direction is
out , this is the name of the calling person(s).If the direction is
in , this is the name of the called person(s).This value is optional. |
There’s two options on how to POST
the relevant data to Zammad.
- Example:
Below calls have been sent with the following configuration. This is important for you to understand the returns we’re showing here.
Outbound:
Destination caller ID
4989*
set outbound caller ID498999998145
with note “All from munich”Destination caller ID
4930*
set outbound caller ID493023125877
“All from Berlin”
Other settings:
Default caller ID for outbound calls
496990009111
POST
-Request sent:
https://{FQDN-Zammad}/api/v1/cti/{instance specific token}
- Outbound
Payload:
{ "event": "answer", "from": "493023125741", "to": "492214710334", "direction": "out", "callId": "9f1840cb-8be9-4d3a-8200-3da2937085f0", "caller": "Christopher Miller" }
Response:
{}
Sample curl command:
$ curl --request POST 'https://{FQDN-Zammad}/api/v1/cti/{instance specific token}' \ --header 'Content-Type: application/json' \ --data-raw '{ "event": "answer", "from": "493023125741", "to": "492214710334", "direction": "out", "callId": "9f1840cb-8be9-4d3a-8200-3da2937085f0", "caller": "Christopher Miller" }'
- Inbound
Payload:
{ "event": "answer", "from": "493023125741", "to": "492214710334", "direction": "in", "callId": "307fa962-de8d-4ffc-817b-7f6993204159", "answeringNumber": "emma@chrispresso.com", "caller": ["Christopher Miller", "Emma Taylor"] }
Response:
{}
Sample curl command:
$ curl --request POST 'https://{FQDN-Zammad}/api/v1/cti/{instance specific token}' \ --header 'Content-Type: application/json' \ --data-raw '{ "event": "answer", "from": "493023125741", "to": "492214710334", "direction": "in", "callId": "307fa962-de8d-4ffc-817b-7f6993204159", "answeringNumber": "emma@chrispresso.com", "caller": ["Christopher Miller", "Emma Taylor"] }'
POST
-Request sent:
https://{FQDN-Zammad}/api/v1/cti/{instance specific token}
- Outbound
Payload:
event:"answer" from:"493023125741" to:"492214710334" direction:"out" callId:"371e2cd7-67ff-4fd9-892b-030c8d128fb1" caller[]:"Christopher Miller" caller[]:"Emma Taylor"
Returns:
{}
Sample curl command:
$ curl --request POST 'https://{FQDN-Zammad}/api/v1/cti/{instance specific token}' \ --form 'event="answer"' \ --form 'from="493023125741"' \ --form 'to="492214710334"' \ --form 'direction="out"' \ --form 'callId="371e2cd7-67ff-4fd9-892b-030c8d128fb1"' \ --form 'caller[]="Christopher Miller"' \ --form 'caller[]="Emma Taylor"'
- Inbound
Payload:
event:"answer" from:"493023125741" to:"492214710334" direction:"in" callId:"61868f1e-2171-4313-970b-25982f0c5ce1" answeringNumber:"emma@chrispresso.com" caller="Emma Taylor"
Returns:
{}
Sample curl command:
$ curl --request POST 'https://{FQDN-Zammad}/api/v1/cti/{instance specific token}' \ --form 'event="answer"' \ --form 'from="493023125741"' \ --form 'to="492214710334"' \ --form 'direction="in"' \ --form 'callId="61868f1e-2171-4313-970b-25982f0c5ce1"' \ --form 'answeringNumber="emma@chrispresso.com"' \ --form 'caller="Emma Taylor"'
- The next logical step within call session context would be:
hangup (call ending)
Online Notification¶
Note
The availability of notification highly depends on the users permission and chosen notification settings.
Please note that the best results are always achieved with Agents.
List¶
Required permission: any
Tip
Use the expand request to know the affected objects. Otherwise you’ll need to find out what ID stands for which object type.
GET
-Request sent: /api/v1/online_notifications?expand=true
Response:
# HTTP-Code 200 Ok
[
{
"id": 4,
"o_id": 6,
"object_lookup_id": 2,
"type_lookup_id": 1,
"user_id": 3,
"seen": false,
"updated_by_id": 8,
"created_by_id": 8,
"created_at": "2021-11-09T13:15:42.628Z",
"updated_at": "2021-11-09T13:15:42.637Z",
"user": "chris@chrispresso.com",
"object": "Ticket",
"type": "create",
"created_by": "emily@example.com",
"updated_by": "emily@example.com"
},
{
"id": 3,
"o_id": 8,
"object_lookup_id": 2,
"type_lookup_id": 2,
"user_id": 3,
"seen": false,
"updated_by_id": 4,
"created_by_id": 4,
"created_at": "2021-11-09T13:10:42.628Z",
"updated_at": "2021-11-09T13:15:42.635Z",
"user": "chris@chrispresso.com",
"object": "Ticket",
"type": "update",
"created_by": "jacob@chrispresso.com",
"updated_by": "jacob@chrispresso.com"
},
{
"id": 2,
"o_id": 3,
"object_lookup_id": 2,
"type_lookup_id": 1,
"user_id": 3,
"seen": true,
"updated_by_id": 6,
"created_by_id": 6,
"created_at": "2021-11-09T12:45:42.625Z",
"updated_at": "2021-11-09T13:15:42.632Z",
"user": "chris@chrispresso.com",
"object": "Ticket",
"type": "create",
"created_by": "anna@example.com",
"updated_by": "anna@example.com"
},
{
"id": 1,
"o_id": 2,
"object_lookup_id": 2,
"type_lookup_id": 1,
"user_id": 3,
"seen": true,
"updated_by_id": 5,
"created_by_id": 5,
"created_at": "2021-11-09T11:45:42.624Z",
"updated_at": "2021-11-09T13:15:42.629Z",
"user": "chris@chrispresso.com",
"object": "Ticket",
"type": "create",
"created_by": "emma@chrispresso.com",
"updated_by": "emma@chrispresso.com"
}
]
Show¶
Required permission: any
GET
-Request sent: /api/v1/online_notifications/{id}
Response:
# HTTP-Code 200 Ok
{
"id": 4,
"o_id": 6,
"object_lookup_id": 2,
"type_lookup_id": 1,
"user_id": 3,
"seen": false,
"updated_by_id": 8,
"created_by_id": 8,
"created_at": "2021-11-09T13:15:42.628Z",
"updated_at": "2021-11-09T13:15:42.637Z"
}
Update¶
Required permission: any
PUT
-Request sent: /api/v1/online_notifications/{id}
{
"seen": true
}
Response:
# HTTP-Code 200 Ok
{
"id": 4,
"seen": true,
"updated_by_id": 3,
"o_id": 6,
"object_lookup_id": 2,
"type_lookup_id": 1,
"user_id": 3,
"created_by_id": 8,
"created_at": "2021-11-09T13:15:42.628Z",
"updated_at": "2021-11-09T13:25:00.004Z"
}
Delete¶
Required permission: any
DELETE
-Request sent: /api/v1/online_notifications/{id}
Response:
# HTTP-Code 200 Ok
{}
Mark all as read¶
Required permission: any
POST
-Request sent: /api/v1/online_notifications/mark_all_as_read
Response:
# HTTP-Code 200 Ok
{}
Object¶
Danger
Adjusting objects via API can cause serious issues with your instance. Proceed with absolute caution and ensure to adjust any of Zammads default fields.
If you want to hide fields, consider Core Workflows instead. For states and priorities use either API endpoints or rails console.
List¶
Required permission: admin.object
GET
-Request sent: /api/v1/object_manager_attributes
Response:
# HTTP-Code 200 Ok
[
{
"id": 2,
"name": "customer_id",
"display": "Customer",
"data_type": "user_autocompletion",
"data_option": {
"relation": "User",
"autocapitalize": false,
"multiple": false,
"guess": true,
"null": false,
"limit": 200,
"placeholder": "Enter Person or Organization/Company",
"minLengt": 2,
"translate": false,
"permission": [
"ticket.agent"
]
},
"data_option_new": {},
"editable": false,
"active": true,
"screens": {
"create_top": {
"-all-": {
"null": false
}
},
"edit": {}
},
"to_create": false,
"to_migrate": false,
"to_delete": false,
"to_config": false,
"position": 10,
"created_by_id": 1,
"updated_by_id": 1,
"created_at": "2021-11-09T13:12:32.677Z",
"updated_at": "2021-11-09T13:12:32.677Z",
"object": "Ticket",
"deletable": false,
"not_deletable_reason": "This attribute is referenced by Overview: My Tickets and thus cannot be deleted!"
},
{
"id": 1,
"name": "title",
"display": "Title",
"data_type": "input",
"data_option": {
"type": "text",
"maxlength": 200,
"null": false,
"translate": false
},
"data_option_new": {},
"editable": false,
"active": true,
"screens": {
"create_top": {
"-all-": {
"null": false
}
},
"edit": {}
},
"to_create": false,
"to_migrate": false,
"to_delete": false,
"to_config": false,
"position": 15,
"created_by_id": 1,
"updated_by_id": 1,
"created_at": "2021-11-09T13:12:32.671Z",
"updated_at": "2021-11-09T13:12:32.671Z",
"object": "Ticket",
"deletable": false
},
{
"id": 3,
"name": "type",
"display": "Type",
"data_type": "select",
"data_option": {
"default": "",
"options": {
"Incident": "Incident",
"Problem": "Problem",
"Request for Change": "Request for Change"
},
"nulloption": true,
"multiple": false,
"null": true,
"translate": true,
"maxlength": 255
},
"data_option_new": {},
"editable": true,
"active": false,
"screens": {
"create_middle": {
"-all-": {
"null": false,
"item_class": "column"
}
},
"edit": {
"ticket.agent": {
"null": false
}
}
},
"to_create": false,
"to_migrate": false,
"to_delete": false,
"to_config": false,
"position": 20,
"created_by_id": 1,
"updated_by_id": 1,
"created_at": "2021-11-09T13:12:32.686Z",
"updated_at": "2021-11-09T13:12:32.686Z",
"object": "Ticket",
"deletable": true
},
{
"id": 4,
"name": "group_id",
"display": "Group",
"data_type": "select",
"data_option": {
"default": "",
"relation": "Group",
"relation_condition": {
"access": "full"
},
"nulloption": true,
"multiple": false,
"null": false,
"translate": false,
"only_shown_if_selectable": true,
"permission": [
"ticket.agent",
"ticket.customer"
],
"maxlength": 255
},
"data_option_new": {},
"editable": false,
"active": true,
"screens": {
"create_middle": {
"-all-": {
"null": false,
"item_class": "column"
}
},
"edit": {
"ticket.agent": {
"null": false
}
}
},
"to_create": false,
"to_migrate": false,
"to_delete": false,
"to_config": false,
"position": 25,
"created_by_id": 1,
"updated_by_id": 1,
"created_at": "2021-11-09T13:12:32.690Z",
"updated_at": "2021-11-09T13:12:32.690Z",
"object": "Ticket",
"deletable": false
},
{
"id": 5,
"name": "owner_id",
"display": "Owner",
"data_type": "select",
"data_option": {
"default": "",
"relation": "User",
"relation_condition": {
"roles": "Agent"
},
"nulloption": true,
"multiple": false,
"null": true,
"translate": false,
"permission": [
"ticket.agent"
],
"maxlength": 255
},
"data_option_new": {},
"editable": false,
"active": true,
"screens": {
"create_middle": {
"-all-": {
"null": true,
"item_class": "column"
}
},
"edit": {
"-all-": {
"null": true
}
}
},
"to_create": false,
"to_migrate": false,
"to_delete": false,
"to_config": false,
"position": 30,
"created_by_id": 1,
"updated_by_id": 1,
"created_at": "2021-11-09T13:12:32.694Z",
"updated_at": "2021-11-09T13:12:32.694Z",
"object": "Ticket",
"deletable": false,
"not_deletable_reason": "This attribute is referenced by Trigger: customer notification (on owner change); Overview: My assigned Tickets,My pending reached Tickets,Unassigned & Open and thus cannot be deleted!"
},
{
"id": 6,
"name": "state_id",
"display": "State",
"data_type": "select",
"data_option": {
"relation": "TicketState",
"nulloption": true,
"multiple": false,
"null": false,
"default": 2,
"translate": true,
"filter": [
2,
1,
3,
4,
6,
7
],
"maxlength": 255
},
"data_option_new": {},
"editable": false,
"active": true,
"screens": {
"create_middle": {
"ticket.agent": {
"null": false,
"item_class": "column",
"filter": [
2,
1,
3,
4,
7
]
},
"ticket.customer": {
"item_class": "column",
"nulloption": false,
"null": true,
"filter": [
1,
4
],
"default": 1
}
},
"edit": {
"ticket.agent": {
"nulloption": false,
"null": false,
"filter": [
2,
3,
4,
7
]
},
"ticket.customer": {
"nulloption": false,
"null": true,
"filter": [
2,
4
],
"default": 2
}
}
},
"to_create": false,
"to_migrate": false,
"to_delete": false,
"to_config": false,
"position": 40,
"created_by_id": 1,
"updated_by_id": 1,
"created_at": "2021-11-09T13:12:32.706Z",
"updated_at": "2021-11-09T13:12:32.706Z",
"object": "Ticket",
"deletable": false,
"not_deletable_reason": "This attribute is referenced by Trigger: auto reply (on new tickets); Overview: My Organization Tickets,My Tickets,My assigned Tickets,My pending reached Tickets,My replacement Tickets,Open,Open Banana Items,Pending reached,Unassigned & Open,VIP Customers and thus cannot be deleted!"
},
{
"id": 7,
"name": "pending_time",
"display": "Pending till",
"data_type": "datetime",
"data_option": {
"future": true,
"past": false,
"diff": 24,
"null": true,
"translate": true,
"permission": [
"ticket.agent"
]
},
"data_option_new": {},
"editable": false,
"active": true,
"screens": {
"create_middle": {
"-all-": {
"null": false,
"item_class": "column"
}
},
"edit": {
"-all-": {
"null": false
}
}
},
"to_create": false,
"to_migrate": false,
"to_delete": false,
"to_config": false,
"position": 41,
"created_by_id": 1,
"updated_by_id": 1,
"created_at": "2021-11-09T13:12:32.713Z",
"updated_at": "2021-11-09T13:12:32.713Z",
"object": "Ticket",
"deletable": false,
"not_deletable_reason": "This attribute is referenced by Overview: My pending reached Tickets,Pending reached and thus cannot be deleted!"
},
{
"id": 8,
"name": "priority_id",
"display": "Priority",
"data_type": "select",
"data_option": {
"relation": "TicketPriority",
"nulloption": false,
"multiple": false,
"null": false,
"default": 2,
"translate": true,
"maxlength": 255
},
"data_option_new": {},
"editable": false,
"active": true,
"screens": {
"create_middle": {
"ticket.agent": {
"null": false,
"item_class": "column"
}
},
"edit": {
"ticket.agent": {
"null": false
}
}
},
"to_create": false,
"to_migrate": false,
"to_delete": false,
"to_config": false,
"position": 80,
"created_by_id": 1,
"updated_by_id": 1,
"created_at": "2021-11-09T13:12:32.718Z",
"updated_at": "2021-11-09T13:12:32.718Z",
"object": "Ticket",
"deletable": false
},
[ ... ]
]
Show¶
Required permission: admin.object
GET
-Request sent: /api/v1/object_manager_attributes/{id}
Response:
# HTTP-Code 200 Ok
{
"id": 18,
"object_lookup_id": 1,
"name": "email",
"display": "Email",
"data_type": "input",
"data_option": {
"type": "email",
"maxlength": 150,
"null": true,
"item_class": "formGroup--halfSize"
},
"data_option_new": {},
"editable": false,
"active": true,
"screens": {
"signup": {
"-all-": {
"null": false
}
},
"invite_agent": {
"-all-": {
"null": false
}
},
"invite_customer": {
"-all-": {
"null": false
}
},
"edit": {
"-all-": {
"null": true
}
},
"create": {
"-all-": {
"null": true
}
},
"view": {
"-all-": {
"shown": true
}
}
},
"to_create": false,
"to_migrate": false,
"to_delete": false,
"to_config": false,
"position": 400,
"created_by_id": 1,
"updated_by_id": 1,
"created_at": "2021-11-09T13:12:32.784Z",
"updated_at": "2021-11-09T13:12:32.784Z"
}
Create¶
Required permission: admin.object
POST
-Request sent: /api/v1/object_manager_attributes
Payload:
{
"name": "sample_boolean",
"object": "Ticket",
"display": "Sample Boolean",
"active": true,
"position": 1550,
"data_type": "boolean",
"data_option": {
"options": {
"true": "very correct indeed",
"false": "very incorrect indeed"
}
},
"screens": {
"create_middle": {
"ticket.customer": {
"shown": true,
"required": false,
"item_class": "column"
},
"ticket.agent": {
"shown": true,
"required": false,
"item_class": "column"
}
},
"edit": {
"ticket.customer": {
"shown": true,
"required": false
},
"ticket.agent": {
"shown": true,
"required": true
}
}
}
}
Response:
# HTTP-Code 201 Created
{
"id": 50,
"object_lookup_id": 2,
"name": "sample_boolean",
"display": "Sample Boolean",
"data_type": "boolean",
"data_option": {
"options": {
"false": "very incorrect indeed",
"true": "very correct indeed"
},
"default": null,
"null": true,
"relation": ""
},
"data_option_new": {},
"editable": true,
"active": true,
"screens": {
"create_middle": {
"ticket.customer": {
"shown": true,
"required": false,
"item_class": "column"
},
"ticket.agent": {
"shown": true,
"required": false,
"item_class": "column"
}
},
"edit": {
"ticket.customer": {
"shown": true,
"required": false
},
"ticket.agent": {
"shown": true,
"required": true
}
}
},
"to_create": true,
"to_migrate": true,
"to_delete": false,
"to_config": false,
"position": 1550,
"created_by_id": 3,
"updated_by_id": 3,
"created_at": "2021-11-12T18:18:23.208Z",
"updated_at": "2021-11-12T18:18:23.208Z"
}
Payload:
{
"name": "sample_date",
"object": "Ticket",
"display": "Sample Date",
"active": true,
"position": 1550,
"data_type": "date",
"data_option": {
"diff": 120
},
"screens": {
"create_middle": {
"ticket.customer": {
"shown": true,
"required": false,
"item_class": "column"
},
"ticket.agent": {
"shown": true,
"required": false,
"item_class": "column"
}
},
"edit": {
"ticket.customer": {
"shown": true,
"required": false
},
"ticket.agent": {
"shown": true,
"required": true
}
}
}
}
Response:
# HTTP-Code 201 Created
{
"id": 51,
"object_lookup_id": 2,
"name": "sample_date",
"display": "Sample Date",
"data_type": "date",
"data_option": {
"diff": 120,
"default": null,
"null": true,
"options": {},
"relation": ""
},
"data_option_new": {},
"editable": true,
"active": true,
"screens": {
"create_middle": {
"ticket.customer": {
"shown": true,
"required": false,
"item_class": "column"
},
"ticket.agent": {
"shown": true,
"required": false,
"item_class": "column"
}
},
"edit": {
"ticket.customer": {
"shown": true,
"required": false
},
"ticket.agent": {
"shown": true,
"required": true
}
}
},
"to_create": true,
"to_migrate": true,
"to_delete": false,
"to_config": false,
"position": 1550,
"created_by_id": 3,
"updated_by_id": 3,
"created_at": "2021-11-12T18:19:32.827Z",
"updated_at": "2021-11-12T18:19:32.827Z"
}
Payload:
{
"name": "sample_datetime",
"object": "Ticket",
"display": "Sample DateTime",
"active": true,
"position": 1550,
"data_type": "datetime",
"data_option": {
"future": true,
"past": false,
"diff": 120
},
"screens": {
"create_middle": {
"ticket.customer": {
"shown": true,
"required": false,
"item_class": "column"
},
"ticket.agent": {
"shown": true,
"required": false,
"item_class": "column"
}
},
"edit": {
"ticket.customer": {
"shown": true,
"required": false
},
"ticket.agent": {
"shown": true,
"required": true
}
}
}
}
Response:
# HTTP-Code 201 Created
{
"id": 52,
"object_lookup_id": 2,
"name": "sample_datetime",
"display": "Sample DateTime",
"data_type": "datetime",
"data_option": {
"future": true,
"past": false,
"diff": 120,
"default": null,
"null": true,
"options": {},
"relation": ""
},
"data_option_new": {},
"editable": true,
"active": true,
"screens": {
"create_middle": {
"ticket.customer": {
"shown": true,
"required": false,
"item_class": "column"
},
"ticket.agent": {
"shown": true,
"required": false,
"item_class": "column"
}
},
"edit": {
"ticket.customer": {
"shown": true,
"required": false
},
"ticket.agent": {
"shown": true,
"required": true
}
}
},
"to_create": true,
"to_migrate": true,
"to_delete": false,
"to_config": false,
"position": 1550,
"created_by_id": 3,
"updated_by_id": 3,
"created_at": "2021-11-12T18:30:38.469Z",
"updated_at": "2021-11-12T18:30:38.469Z"
}
Payload:
{
"name": "sample_integer",
"object": "Ticket",
"display": "Sample Integer",
"active": true,
"position": 1550,
"data_type": "integer",
"data_option": {
"default": 1234,
"min": 4,
"max": 8
},
"screens": {
"create_middle": {
"ticket.customer": {
"shown": true,
"required": false,
"item_class": "column"
},
"ticket.agent": {
"shown": true,
"required": false,
"item_class": "column"
}
},
"edit": {
"ticket.customer": {
"shown": true,
"required": false
},
"ticket.agent": {
"shown": true,
"required": true
}
}
}
}
Response:
# HTTP-Code 201 Created
{
"id": 53,
"object_lookup_id": 2,
"name": "sample_integer",
"display": "Sample Integer",
"data_type": "integer",
"data_option": {
"default": 1234,
"min": 4,
"max": 8,
"null": true,
"options": {},
"relation": ""
},
"data_option_new": {},
"editable": true,
"active": true,
"screens": {
"create_middle": {
"ticket.customer": {
"shown": true,
"required": false,
"item_class": "column"
},
"ticket.agent": {
"shown": true,
"required": false,
"item_class": "column"
}
},
"edit": {
"ticket.customer": {
"shown": true,
"required": false
},
"ticket.agent": {
"shown": true,
"required": true
}
}
},
"to_create": true,
"to_migrate": true,
"to_delete": false,
"to_config": false,
"position": 1550,
"created_by_id": 3,
"updated_by_id": 3,
"created_at": "2021-11-12T18:32:14.213Z",
"updated_at": "2021-11-12T18:32:14.213Z"
}
Payload:
{
"name": "sample_select",
"object": "Ticket",
"display": "Sample Select",
"active": true,
"position": 1550,
"data_type": "select",
"data_option": {
"options": {
"key-one": "First Key",
"key-two": "Second Key",
"key-three": "Third Key"
},
"default": "key-two",
"linktemplate": ""
},
"screens": {
"create_middle": {
"ticket.customer": {
"shown": true,
"required": false,
"item_class": "column"
},
"ticket.agent": {
"shown": true,
"required": false,
"item_class": "column"
}
},
"edit": {
"ticket.customer": {
"shown": true,
"required": false
},
"ticket.agent": {
"shown": true,
"required": true
}
}
}
}
Response:
# HTTP-Code 201 Created
{
"id": 54,
"object_lookup_id": 2,
"name": "sample_select",
"display": "Sample Select",
"data_type": "select",
"data_option": {
"options": {
"key-one": "First Key",
"key-two": "Second Key",
"key-three": "Third Key"
},
"default": "key-two",
"linktemplate": "",
"null": true,
"relation": "",
"nulloption": true,
"maxlength": 255
},
"data_option_new": {},
"editable": true,
"active": true,
"screens": {
"create_middle": {
"ticket.customer": {
"shown": true,
"required": false,
"item_class": "column"
},
"ticket.agent": {
"shown": true,
"required": false,
"item_class": "column"
}
},
"edit": {
"ticket.customer": {
"shown": true,
"required": false
},
"ticket.agent": {
"shown": true,
"required": true
}
}
},
"to_create": true,
"to_migrate": true,
"to_delete": false,
"to_config": false,
"position": 1550,
"created_by_id": 3,
"updated_by_id": 3,
"created_at": "2021-11-12T18:34:08.711Z",
"updated_at": "2021-11-12T18:34:08.711Z"
}
Payload:
{
"name": "sample_text",
"object": "Ticket",
"display": "Sample Text",
"active": true,
"position": 1550,
"data_type": "input",
"data_option": {
"default": "amazing default",
"type": "text",
"maxlength": 120,
"linktemplate": "https://www.google.com/search?q=#{ticket.sample_text}"
},
"screens": {
"create_middle": {
"ticket.customer": {
"shown": true,
"required": false,
"item_class": "column"
},
"ticket.agent": {
"shown": true,
"required": false,
"item_class": "column"
}
},
"edit": {
"ticket.customer": {
"shown": true,
"required": false
},
"ticket.agent": {
"shown": true,
"required": true
}
}
}
}
Hint
Zammad input fields can have 4 different types:
tel
text
url
⚠ URL does not support link-templates ⚠
Depending on the chosen input type, Zammad expects different formats of data. E.g.: email demands a email address to be provided.
Response:
# HTTP-Code 201 Created
{
"id": 55,
"object_lookup_id": 2,
"name": "sample_text",
"display": "Sample Text",
"data_type": "input",
"data_option": {
"default": "amazing default",
"type": "text",
"maxlength": 120,
"linktemplate": "https://www.google.com/search?q=#{ticket.sample_text}",
"null": true,
"options": {},
"relation": ""
},
"data_option_new": {},
"editable": true,
"active": true,
"screens": {
"create_middle": {
"ticket.customer": {
"shown": true,
"required": false,
"item_class": "column"
},
"ticket.agent": {
"shown": true,
"required": false,
"item_class": "column"
}
},
"edit": {
"ticket.customer": {
"shown": true,
"required": false
},
"ticket.agent": {
"shown": true,
"required": true
}
}
},
"to_create": true,
"to_migrate": true,
"to_delete": false,
"to_config": false,
"position": 1550,
"created_by_id": 3,
"updated_by_id": 3,
"created_at": "2021-11-12T18:41:38.031Z",
"updated_at": "2021-11-12T18:41:38.031Z"
}
Payload:
{
"name": "sample_treeselect",
"object": "Ticket",
"display": "Sample Tree Select",
"active": true,
"position": 1550,
"data_type": "tree_select",
"data_option": {
"options": [
{
"name": "row one - maximum child depth",
"value": "row one - maximum child depth",
"children": [
{
"name": "row one child level one",
"value": "row one - maximum child depth::row one child level one",
"children": [
{
"name": "row one child level two",
"value": "row one - maximum child depth::row one child level one::row one child level two",
"children": [
{
"name": "row one child level three",
"value": "row one - maximum child depth::row one child level one::row one child level two::row one child level three",
"children": [
{
"name": "row one child level four",
"value": "row one - maximum child depth::row one child level one::row one child level two::row one child level three::row one child level four",
"children": [
{
"name": "row one child level fize",
"value": "row one - maximum child depth::row one child level one::row one child level two::row one child level three::row one child level four::row one child level fize"
}
]
}
]
}
]
}
]
}
]
},
{
"name": "row two - no childs",
"value": "row two - no childs"
},
{
"name": "row three - one child",
"value": "row three - one child",
"children": [
{
"name": "row three - first and only child",
"value": "row three - one child::row three - first and only child"
}
]
}
]
},
"screens": {
"create_middle": {
"ticket.customer": {
"shown": true,
"required": false,
"item_class": "column"
},
"ticket.agent": {
"shown": true,
"required": false,
"item_class": "column"
}
},
"edit": {
"ticket.customer": {
"shown": true,
"required": false
},
"ticket.agent": {
"shown": true,
"required": true
}
}
}
}
Response:
# HTTP-Code 201 Created
{
"id": 56,
"object_lookup_id": 2,
"name": "sample_treeselect",
"display": "Sample Tree Select",
"data_type": "tree_select",
"data_option": {
"options": [
{
"name": "row one - maximum child depth",
"value": "row one - maximum child depth",
"children": [
{
"name": "row one child level one",
"value": "row one - maximum child depth::row one child level one",
"children": [
{
"name": "row one child level two",
"value": "row one - maximum child depth::row one child level one::row one child level two",
"children": [
{
"name": "row one child level three",
"value": "row one - maximum child depth::row one child level one::row one child level two::row one child level three",
"children": [
{
"name": "row one child level four",
"value": "row one - maximum child depth::row one child level one::row one child level two::row one child level three::row one child level four",
"children": [
{
"name": "row one child level fize",
"value": "row one - maximum child depth::row one child level one::row one child level two::row one child level three::row one child level four::row one child level fize"
}
]
}
]
}
]
}
]
}
]
},
{
"name": "row two - no childs",
"value": "row two - no childs"
},
{
"name": "row three - one child",
"value": "row three - one child",
"children": [
{
"name": "row three - first and only child",
"value": "row three - one child::row three - first and only child"
}
]
}
],
"default": "",
"null": true,
"relation": "",
"nulloption": true,
"maxlength": 255
},
"data_option_new": {},
"editable": true,
"active": true,
"screens": {
"create_middle": {
"ticket.customer": {
"shown": true,
"required": false,
"item_class": "column"
},
"ticket.agent": {
"shown": true,
"required": false,
"item_class": "column"
}
},
"edit": {
"ticket.customer": {
"shown": true,
"required": false
},
"ticket.agent": {
"shown": true,
"required": true
}
}
},
"to_create": true,
"to_migrate": true,
"to_delete": false,
"to_config": false,
"position": 1550,
"created_by_id": 3,
"updated_by_id": 3,
"created_at": "2021-11-12T18:48:15.485Z",
"updated_at": "2021-11-12T18:48:15.485Z"
}
Note
Please note that above payloads cover ticket objects. This is fine in most situations, except if you’re looking at the default object permissions. This is why we’re listing these separate for you to view.
The attribute object
controls which context is being used:
Ticket
User
Organisation
Group
"screens": {
"create_middle": {
"ticket.customer": {
"shown": true,
"required": false,
"item_class": "column"
},
"ticket.agent": {
"shown": true,
"required": false,
"item_class": "column"
}
},
"edit": {
"ticket.customer": {
"shown": true,
"required": false
},
"ticket.agent": {
"shown": true,
"required": true
}
}
}
"screens": {
"create": {
"ticket.customer": {
"shown": true,
"required": false
},
"ticket.agent": {
"shown": true,
"required": false
},
"admin.user": {
"shown": true,
"required": false
}
},
"view": {
"ticket.customer": {
"shown": true
},
"ticket.agent": {
"shown": true
},
"admin.user": {
"shown": true
}
},
"edit": {
"ticket.agent": {
"shown": true,
"required": false
},
"admin.user": {
"shown": true,
"required": false
}
},
"signup": {
"ticket.customer": {
"shown": false,
"required": false
}
},
"invite_customer": {
"ticket.agent": {
"shown": false,
"required": false
},
"admin.user": {
"shown": false,
"required": false
}
},
"invite_agent": {
"admin.user": {
"shown": false,
"required": false
}
}
}
"screens": {
"view": {
"ticket.customer": {
"shown": true
},
"ticket.agent": {
"shown": true
},
"admin.organization": {
"shown": true
}
},
"create": {
"ticket.agent": {
"shown": true,
"required": false
},
"admin.organization": {
"shown": true,
"required": false
}
},
"edit": {
"ticket.agent": {
"shown": true,
"required": false
},
"admin.organization": {
"shown": true,
"required": false
}
}
}
"screens": {
"create": {
"admin.group": {
"shown": true,
"required": false
}
},
"edit": {
"admin.group": {
"shown": true,
"required": false
}
},
"view": {
"admin.group": {
"shown": true
}
}
}
Update¶
Required permission: admin.object
Except on the request method, payloads or updating and creating objects are identical. For full payload samples thus scroll up to Create.
Zammad will return two attributes during update: data_option
and
data_option_new
. The first attribute contains the current active values
and the second one the new to be values
(they’ll become active after executing the database migrations).
PUT
-Request sent: /api/v1/object_manager_attributes/{id}
{
"id": 50,
"name": "sample_boolean",
"object": "Ticket",
"display": "Sample Boolean",
"data_type": "boolean",
"position": 1200,
"data_option": {
"options": {
"true": "yes",
"false": "no"
},
"default": "false"
}
}
Note
Ensure to provide data_option
. Zammad is very picky if you leave out
this attribute. Please note that changing the object type after creation
is not possible.
Response:
# HTTP-Code 200 Ok
{
"name": "sample_boolean",
"display": "Sample Boolean",
"data_type": "boolean",
"position": 1200,
"data_option_new": {
"options": {
"false": "no",
"true": "yes"
},
"default": false,
"null": true,
"relation": ""
},
"data_option": {
"options": {
"false": "very incorrect indeed",
"true": "very correct indeed"
},
"default": null,
"null": true,
"relation": ""
},
"object_lookup_id": 2,
"to_config": true,
"editable": true,
"id": 50,
"updated_by_id": 3,
"active": true,
"screens": {
"create_middle": {
"ticket.customer": {
"shown": true,
"required": false,
"item_class": "column"
},
"ticket.agent": {
"shown": true,
"required": false,
"item_class": "column"
}
},
"edit": {
"ticket.customer": {
"shown": true,
"required": false
},
"ticket.agent": {
"shown": true,
"required": true
}
}
},
"to_create": false,
"to_migrate": false,
"to_delete": false,
"created_by_id": 3,
"created_at": "2021-11-12T18:18:23.208Z",
"updated_at": "2021-11-12T19:30:20.883Z"
}
Delete¶
Required permission: admin.object
DELETE
-Request sent: /api/v1/object_manager_attributes/{id}
Response:
# HTTP-Code 200 Ok
{}
Execute Database Migrations¶
Required permission: admin.object
Warning
After executing the database migrations a restart of Zammad is mandatory. If configured Zammad also can restart automatically (this is the case on Hosted environments) – expect a short downtime.
POST
-Request sent: /api/v1/object_manager_attributes_execute_migrations
Response:
# HTTP-Code 200 Ok
{}
User Access Token¶
List¶
Required permission: user_preferences.access_token
GET
-Request sent: /api/v1/user_access_token
Response:
# HTTP-Code 200 Ok
{
"tokens": [
{
"id": 2,
"user_id": 3,
"action": "api",
"label": "test",
"preferences": {
"permission": [
"user_preferences.access_token"
]
},
"last_used_at": "2021-11-11T14:29:22.765Z",
"expires_at": null,
"created_at": "2021-11-10T23:17:46.570Z",
"updated_at": "2021-11-11T14:29:22.765Z"
},
{
"id": 1,
"user_id": 3,
"action": "api",
"label": "full",
"preferences": {
"permission": [
"admin",
"ticket.agent"
]
},
"last_used_at": "2021-11-10T23:12:06.078Z",
"expires_at": null,
"created_at": "2021-11-09T13:17:20.446Z",
"updated_at": "2021-11-10T23:12:06.078Z"
}
],
"permissions": [
{
"id": 1,
"name": "admin",
"note": "Admin Interface",
"preferences": {},
"active": true,
"allow_signup": false,
"created_at": "2021-11-09T13:12:31.544Z",
"updated_at": "2021-11-09T13:12:31.544Z"
},
{
"id": 32,
"name": "admin.api",
"note": "Manage %s",
"preferences": {
"translations": [
"API"
]
},
"active": true,
"allow_signup": false,
"created_at": "2021-11-09T13:12:31.754Z",
"updated_at": "2021-11-09T13:12:31.754Z"
},
{
"id": 26,
"name": "admin.branding",
"note": "Manage %s",
"preferences": {
"translations": [
"Branding"
]
},
"active": true,
"allow_signup": false,
"created_at": "2021-11-09T13:12:31.713Z",
"updated_at": "2021-11-09T13:12:31.713Z"
},
{
"id": 11,
"name": "admin.calendar",
"note": "Manage %s",
"preferences": {
"translations": [
"Calendar"
]
},
"active": true,
"allow_signup": false,
"created_at": "2021-11-09T13:12:31.626Z",
"updated_at": "2021-11-09T13:12:31.626Z"
},
{
"id": 25,
"name": "admin.channel_chat",
"note": "Manage %s",
"preferences": {
"translations": [
"Channel - Chat"
]
},
"active": true,
"allow_signup": false,
"created_at": "2021-11-09T13:12:31.704Z",
"updated_at": "2021-11-09T13:12:31.704Z"
},
{
"id": 18,
"name": "admin.channel_email",
"note": "Manage %s",
"preferences": {
"translations": [
"Channel - Email"
]
},
"active": true,
"allow_signup": false,
"created_at": "2021-11-09T13:12:31.665Z",
"updated_at": "2021-11-09T13:12:31.665Z"
},
{
"id": 20,
"name": "admin.channel_facebook",
"note": "Manage %s",
"preferences": {
"translations": [
"Channel - Facebook"
]
},
"active": true,
"allow_signup": false,
"created_at": "2021-11-09T13:12:31.676Z",
"updated_at": "2021-11-09T13:12:31.676Z"
},
{
"id": 17,
"name": "admin.channel_formular",
"note": "Manage %s",
"preferences": {
"translations": [
"Channel - Formular"
]
},
"active": true,
"allow_signup": false,
"created_at": "2021-11-09T13:12:31.659Z",
"updated_at": "2021-11-09T13:12:31.659Z"
},
{
"id": 22,
"name": "admin.channel_google",
"note": "Manage %s",
"preferences": {
"translations": [
"Channel - Google"
]
},
"active": true,
"allow_signup": false,
"created_at": "2021-11-09T13:12:31.687Z",
"updated_at": "2021-11-09T13:12:31.687Z"
},
{
"id": 23,
"name": "admin.channel_microsoft365",
"note": "Manage %s",
"preferences": {
"translations": [
"Channel - Microsoft 365"
]
},
"active": true,
"allow_signup": false,
"created_at": "2021-11-09T13:12:31.693Z",
"updated_at": "2021-11-09T13:12:31.693Z"
},
{
"id": 24,
"name": "admin.channel_sms",
"note": "Manage %s",
"preferences": {
"translations": [
"Channel - SMS"
]
},
"active": true,
"allow_signup": false,
"created_at": "2021-11-09T13:12:31.699Z",
"updated_at": "2021-11-09T13:12:31.699Z"
},
{
"id": 21,
"name": "admin.channel_telegram",
"note": "Manage %s",
"preferences": {
"translations": [
"Channel - Telegram"
]
},
"active": true,
"allow_signup": false,
"created_at": "2021-11-09T13:12:31.682Z",
"updated_at": "2021-11-09T13:12:31.682Z"
},
{
"id": 19,
"name": "admin.channel_twitter",
"note": "Manage %s",
"preferences": {
"translations": [
"Channel - Twitter"
]
},
"active": true,
"allow_signup": false,
"created_at": "2021-11-09T13:12:31.671Z",
"updated_at": "2021-11-09T13:12:31.671Z"
},
{
"id": 16,
"name": "admin.channel_web",
"note": "Manage %s",
"preferences": {
"translations": [
"Channel - Web"
]
},
"active": true,
"allow_signup": false,
"created_at": "2021-11-09T13:12:31.654Z",
"updated_at": "2021-11-09T13:12:31.654Z"
},
{
"id": 40,
"name": "admin.core_workflow",
"note": "Manage %s",
"preferences": {
"translations": [
"Core Workflow"
]
},
"active": true,
"allow_signup": false,
"created_at": "2021-11-09T13:12:31.807Z",
"updated_at": "2021-11-09T13:12:31.807Z"
},
{
"id": 36,
"name": "admin.data_privacy",
"note": "Manage %s",
"preferences": {
"translations": [
"Data Privacy"
]
},
"active": true,
"allow_signup": false,
"created_at": "2021-11-09T13:12:31.783Z",
"updated_at": "2021-11-09T13:12:31.783Z"
},
{
"id": 3,
"name": "admin.group",
"note": "Manage %s",
"preferences": {
"translations": [
"Groups"
]
},
"active": true,
"allow_signup": false,
"created_at": "2021-11-09T13:12:31.565Z",
"updated_at": "2021-11-09T13:12:31.565Z"
},
{
"id": 31,
"name": "admin.integration",
"note": "Manage %s",
"preferences": {
"translations": [
"Integrations"
]
},
"active": true,
"allow_signup": false,
"created_at": "2021-11-09T13:12:31.748Z",
"updated_at": "2021-11-09T13:12:31.748Z"
},
{
"id": 59,
"name": "admin.knowledge_base",
"note": "Create and setup %s",
"preferences": {
"translations": [
"Knowledge Base"
]
},
"active": true,
"allow_signup": false,
"created_at": "2021-11-09T13:12:31.992Z",
"updated_at": "2021-11-09T13:12:31.992Z"
},
{
"id": 9,
"name": "admin.macro",
"note": "Manage %s",
"preferences": {
"translations": [
"Macros"
]
},
"active": true,
"allow_signup": false,
"created_at": "2021-11-09T13:12:31.612Z",
"updated_at": "2021-11-09T13:12:31.612Z"
},
{
"id": 37,
"name": "admin.maintenance",
"note": "Manage %s",
"preferences": {
"translations": [
"Maintenance"
]
},
"active": true,
"allow_signup": false,
"created_at": "2021-11-09T13:12:31.789Z",
"updated_at": "2021-11-09T13:12:31.789Z"
},
{
"id": 35,
"name": "admin.monitoring",
"note": "Manage %s",
"preferences": {
"translations": [
"Monitoring"
]
},
"active": true,
"allow_signup": false,
"created_at": "2021-11-09T13:12:31.777Z",
"updated_at": "2021-11-09T13:12:31.777Z"
},
{
"id": 33,
"name": "admin.object",
"note": "Manage %s",
"preferences": {
"translations": [
"Objects"
]
},
"active": true,
"allow_signup": false,
"created_at": "2021-11-09T13:12:31.760Z",
"updated_at": "2021-11-09T13:12:31.760Z"
},
{
"id": 5,
"name": "admin.organization",
"note": "Manage %s",
"preferences": {
"translations": [
"Organizations"
]
},
"active": true,
"allow_signup": false,
"created_at": "2021-11-09T13:12:31.579Z",
"updated_at": "2021-11-09T13:12:31.579Z"
},
{
"id": 6,
"name": "admin.overview",
"note": "Manage %s",
"preferences": {
"translations": [
"Overviews"
]
},
"active": true,
"allow_signup": false,
"created_at": "2021-11-09T13:12:31.591Z",
"updated_at": "2021-11-09T13:12:31.591Z"
},
{
"id": 30,
"name": "admin.package",
"note": "Manage %s",
"preferences": {
"translations": [
"Packages"
]
},
"active": true,
"allow_signup": false,
"created_at": "2021-11-09T13:12:31.738Z",
"updated_at": "2021-11-09T13:12:31.738Z"
},
{
"id": 15,
"name": "admin.report_profile",
"note": "Manage %s",
"preferences": {
"translations": [
"Report Profiles"
]
},
"active": true,
"allow_signup": false,
"created_at": "2021-11-09T13:12:31.648Z",
"updated_at": "2021-11-09T13:12:31.648Z"
},
{
"id": 4,
"name": "admin.role",
"note": "Manage %s",
"preferences": {
"translations": [
"Roles"
]
},
"active": true,
"allow_signup": false,
"created_at": "2021-11-09T13:12:31.572Z",
"updated_at": "2021-11-09T13:12:31.572Z"
},
{
"id": 14,
"name": "admin.scheduler",
"note": "Manage %s",
"preferences": {
"translations": [
"Scheduler"
]
},
"active": true,
"allow_signup": false,
"created_at": "2021-11-09T13:12:31.642Z",
"updated_at": "2021-11-09T13:12:31.642Z"
},
{
"id": 28,
"name": "admin.security",
"note": "Manage %s Settings",
"preferences": {
"translations": [
"Security"
]
},
"active": true,
"allow_signup": false,
"created_at": "2021-11-09T13:12:31.725Z",
"updated_at": "2021-11-09T13:12:31.725Z"
},
{
"id": 38,
"name": "admin.session",
"note": "Manage %s",
"preferences": {
"translations": [
"Sessions"
]
},
"active": true,
"allow_signup": false,
"created_at": "2021-11-09T13:12:31.795Z",
"updated_at": "2021-11-09T13:12:31.795Z"
},
{
"id": 27,
"name": "admin.setting_system",
"note": "Manage %s Settings",
"preferences": {
"translations": [
"System"
]
},
"active": true,
"allow_signup": false,
"created_at": "2021-11-09T13:12:31.719Z",
"updated_at": "2021-11-09T13:12:31.719Z"
},
{
"id": 12,
"name": "admin.sla",
"note": "Manage %s",
"preferences": {
"translations": [
"SLA"
]
},
"active": true,
"allow_signup": false,
"created_at": "2021-11-09T13:12:31.631Z",
"updated_at": "2021-11-09T13:12:31.631Z"
},
{
"id": 10,
"name": "admin.tag",
"note": "Manage %s",
"preferences": {
"translations": [
"Tags"
]
},
"active": true,
"allow_signup": false,
"created_at": "2021-11-09T13:12:31.619Z",
"updated_at": "2021-11-09T13:12:31.619Z"
},
{
"id": 7,
"name": "admin.text_module",
"note": "Manage %s",
"preferences": {
"translations": [
"Text Modules"
]
},
"active": true,
"allow_signup": false,
"created_at": "2021-11-09T13:12:31.598Z",
"updated_at": "2021-11-09T13:12:31.598Z"
},
{
"id": 29,
"name": "admin.ticket",
"note": "Manage %s Settings",
"preferences": {
"translations": [
"Ticket"
]
},
"active": true,
"allow_signup": false,
"created_at": "2021-11-09T13:12:31.731Z",
"updated_at": "2021-11-09T13:12:31.731Z"
},
{
"id": 8,
"name": "admin.time_accounting",
"note": "Manage %s",
"preferences": {
"translations": [
"Time Accounting"
]
},
"active": true,
"allow_signup": false,
"created_at": "2021-11-09T13:12:31.606Z",
"updated_at": "2021-11-09T13:12:31.606Z"
},
{
"id": 34,
"name": "admin.translation",
"note": "Manage %s",
"preferences": {
"translations": [
"Translations"
]
},
"active": true,
"allow_signup": false,
"created_at": "2021-11-09T13:12:31.767Z",
"updated_at": "2021-11-09T13:12:31.767Z"
},
{
"id": 13,
"name": "admin.trigger",
"note": "Manage %s",
"preferences": {
"translations": [
"Triggers"
]
},
"active": true,
"allow_signup": false,
"created_at": "2021-11-09T13:12:31.637Z",
"updated_at": "2021-11-09T13:12:31.637Z"
},
{
"id": 2,
"name": "admin.user",
"note": "Manage %s",
"preferences": {
"translations": [
"Users"
]
},
"active": true,
"allow_signup": false,
"created_at": "2021-11-09T13:12:31.556Z",
"updated_at": "2021-11-09T13:12:31.556Z"
},
{
"id": 39,
"name": "admin.webhook",
"note": "Manage %s",
"preferences": {
"translations": [
"Webhooks"
]
},
"active": true,
"allow_signup": false,
"created_at": "2021-11-09T13:12:31.801Z",
"updated_at": "2021-11-09T13:12:31.801Z"
},
{
"id": 55,
"name": "chat",
"note": "Access to %s",
"preferences": {
"translations": [
"Chat"
],
"disabled": true
},
"active": true,
"allow_signup": false,
"created_at": "2021-11-09T13:12:31.901Z",
"updated_at": "2021-11-09T13:12:31.901Z"
},
{
"id": 56,
"name": "chat.agent",
"note": "Access to %s",
"preferences": {
"translations": [
"Chat"
]
},
"active": true,
"allow_signup": false,
"created_at": "2021-11-09T13:12:31.909Z",
"updated_at": "2021-11-09T13:12:31.909Z"
},
{
"id": 57,
"name": "cti",
"note": "CTI",
"preferences": {
"disabled": true
},
"active": true,
"allow_signup": false,
"created_at": "2021-11-09T13:12:31.916Z",
"updated_at": "2021-11-09T13:12:31.916Z"
},
{
"id": 58,
"name": "cti.agent",
"note": "Access to %s",
"preferences": {
"translations": [
"CTI"
]
},
"active": true,
"allow_signup": false,
"created_at": "2021-11-09T13:12:31.922Z",
"updated_at": "2021-11-09T13:12:31.922Z"
},
{
"id": 60,
"name": "knowledge_base",
"note": "Manage %s",
"preferences": {
"translations": [
"Knowledge Base"
],
"disabled": true
},
"active": true,
"allow_signup": false,
"created_at": "2021-11-09T13:12:31.999Z",
"updated_at": "2021-11-09T13:12:31.999Z"
},
{
"id": 61,
"name": "knowledge_base.editor",
"note": "Manage %s",
"preferences": {
"translations": [
"Knowledge Base Editor"
]
},
"active": true,
"allow_signup": false,
"created_at": "2021-11-09T13:12:32.006Z",
"updated_at": "2021-11-09T13:12:32.006Z"
},
{
"id": 62,
"name": "knowledge_base.reader",
"note": "Manage %s",
"preferences": {
"translations": [
"Knowledge Base Reader"
]
},
"active": true,
"allow_signup": false,
"created_at": "2021-11-09T13:12:32.012Z",
"updated_at": "2021-11-09T13:12:32.012Z"
},
{
"id": 51,
"name": "report",
"note": "Report Interface",
"preferences": {},
"active": true,
"allow_signup": false,
"created_at": "2021-11-09T13:12:31.875Z",
"updated_at": "2021-11-09T13:12:31.875Z"
},
{
"id": 52,
"name": "ticket",
"note": "Ticket Interface",
"preferences": {
"disabled": true
},
"active": true,
"allow_signup": false,
"created_at": "2021-11-09T13:12:31.880Z",
"updated_at": "2021-11-09T13:12:31.880Z"
},
{
"id": 53,
"name": "ticket.agent",
"note": "Access to Agent Tickets based on Group Access",
"preferences": {
"plugin": [
"groups"
]
},
"active": true,
"allow_signup": false,
"created_at": "2021-11-09T13:12:31.888Z",
"updated_at": "2021-11-09T13:12:31.888Z"
},
{
"id": 41,
"name": "user_preferences",
"note": "User Preferences",
"preferences": {},
"active": true,
"allow_signup": true,
"created_at": "2021-11-09T13:12:31.812Z",
"updated_at": "2021-11-09T13:12:31.812Z"
},
{
"id": 44,
"name": "user_preferences.access_token",
"note": "Manage %s",
"preferences": {
"translations": [
"Token Access"
]
},
"active": true,
"allow_signup": true,
"created_at": "2021-11-09T13:12:31.829Z",
"updated_at": "2021-11-09T13:12:31.829Z"
},
{
"id": 48,
"name": "user_preferences.avatar",
"note": "Manage %s",
"preferences": {
"translations": [
"Avatar"
]
},
"active": true,
"allow_signup": true,
"created_at": "2021-11-09T13:12:31.852Z",
"updated_at": "2021-11-09T13:12:31.852Z"
},
{
"id": 49,
"name": "user_preferences.calendar",
"note": "Access to %s",
"preferences": {
"translations": [
"Calendars"
],
"required": [
"ticket.agent"
]
},
"active": true,
"allow_signup": true,
"created_at": "2021-11-09T13:12:31.857Z",
"updated_at": "2021-11-09T13:12:31.857Z"
},
{
"id": 47,
"name": "user_preferences.device",
"note": "Manage %s",
"preferences": {
"translations": [
"Devices"
]
},
"active": true,
"allow_signup": true,
"created_at": "2021-11-09T13:12:31.847Z",
"updated_at": "2021-11-09T13:12:31.847Z"
},
{
"id": 45,
"name": "user_preferences.language",
"note": "Change %s",
"preferences": {
"translations": [
"Language"
]
},
"active": true,
"allow_signup": true,
"created_at": "2021-11-09T13:12:31.834Z",
"updated_at": "2021-11-09T13:12:31.834Z"
},
{
"id": 46,
"name": "user_preferences.linked_accounts",
"note": "Manage %s",
"preferences": {
"translations": [
"Linked Accounts"
]
},
"active": true,
"allow_signup": true,
"created_at": "2021-11-09T13:12:31.840Z",
"updated_at": "2021-11-09T13:12:31.840Z"
},
{
"id": 43,
"name": "user_preferences.notifications",
"note": "Manage %s",
"preferences": {
"translations": [
"Notifications"
],
"required": [
"ticket.agent"
]
},
"active": true,
"allow_signup": true,
"created_at": "2021-11-09T13:12:31.823Z",
"updated_at": "2021-11-09T13:12:31.823Z"
},
{
"id": 50,
"name": "user_preferences.out_of_office",
"note": "Change %s",
"preferences": {
"translations": [
"Out of Office"
],
"required": [
"ticket.agent"
]
},
"active": true,
"allow_signup": true,
"created_at": "2021-11-09T13:12:31.868Z",
"updated_at": "2021-11-09T13:12:31.868Z"
},
{
"id": 42,
"name": "user_preferences.password",
"note": "Change %s",
"preferences": {
"translations": [
"Password"
]
},
"active": true,
"allow_signup": true,
"created_at": "2021-11-09T13:12:31.818Z",
"updated_at": "2021-11-09T13:12:31.818Z"
}
]
}
Create¶
Required permission: user_preferences.access_token
POST
-Request sent: /api/v1/user_access_token
{
"name": "My amazing test",
"permission": ["cti.agent","ticket.agent"],
"expires_at": "2021-12-21"
}
Response:
# HTTP-Code 200 Ok
{
"token": "M4oXJgB_8WiMNWzSdrDv3K3YXJywDh52BqC7IKV-NnM_Cf_bd_SkS6zyIWNZKJXw"
}
Note
Above returned token
is the API token. This value is provided once after
creation and can’t be retrieved after.
Delete¶
Required permission: user_preferences.access_token
DELETE
-Request sent: /api/v1/user_access_token/{id}
Response:
# HTTP-Code 200 Ok
{}
Backup and Restore¶
Zammad comes with a collection of scripts for easy backup & restore for default
installations. These scripts are located within /opt/zammad/contrib/backup
.
Warning
⚖️ Important things to note beforehand
These scripts do not come with any warranty and may not work in your specific use case. This depends on the configuration and installation type of your instance.
You should always regularly test and review the functionality! If the script functionality or scope is not working for your cases, feel free to copy these to a independent location and adjust the scripts as needed.
Getting Started¶
Backup configuration¶
Before you can run either a backup or restoration, the scripts requires you
to provide a configuration file. We’re shipping a config.dist
within the
/opt/zammad/contrib/backup
directory which you can simply rename.
To do so run the following commands as either root
or zammad
user.
$ cd /opt/zammad/contrib/backup/
$ mv config.dist config
If below default values are not working for you or your installation in general, this is the best moment to adjust the configuration file as needed.
After this you’ll be ready to continue with either creating your first backup or restoring an existing backup.
BACKUP_DIR
Default:
/var/tmp/zammad_backup
Tell the backup script where to write your backup files to.
Ensure that the user you’re going to use for backing up Zammad (either
root
orzammad
by default) has enough permissions to write into the target directory structure.In case the directory is not available yet, the backup script will attempt to create the directory.
Make also sure to have enough space available on the backup location. Zammad always creates full backups. While we do compress backups, expect worst case ratios of 1 (no compression at all) depending on your attachments!
HOLD_DAYS
Default:
10
How many days should the backup script keep old backups? This value contains 60 minutes grace period (so e.g. 10 days plus 1 hour) for safety reasons.
Old backups are removed before creating the actual (current) backup.
Example:
0
will keep the last 25 hours worth of backup-1
will always remove all available backups (aka only keep current backup)
FULL_FS_DUMP
Default:
yes
(accepts:yes
orno
)Setting this option to
no
allows you to only backup usage data without any environmental files from your old host. This allows you to backup your Zammad database together with the attachments you’ve stored within the file system.Please refer Storage Settings to learn how to change the storage location of your attachments.
If you can’t decide, our clear suggestion is setting this to
no
.DEBUG
Default:
no
(accepts:yes
orno
)Having issues and want to fiddle around? Setting this option to
yes
may help you with this. It contains useful debug messages at strategic points.Warning
This option potentially returns sensitive information to standard out! Do not use this option in productive environments or ensure to turn it off after testing.
Create Backup¶
Preparation¶
Before running your first backup, please have a look at the Backup configuration to set it up correctly.
Backup¶
In general, running a Zammad backup is as simple as running:
$ /opt/zammad/contrib/backup/zammad_backup.sh
Please make sure to test the backup function manually with the user you’re planning to backup first. This ensures that your backup really is running as expected.
The backup process should look like this one:
# Zammad backup started - Fri Jan 21 17:53:44 CET 2022!
creating file backup...
... as full dump
creating postgresql backup...
Ensuring dump permissions ...
# Zammad backuped successfully - Fri Jan 21 17:53:57 CET 2022!
Sample backup process with default settings.
Additional Information¶
The backup script can be either run as
zammad
orroot
user.Stopping Zammad is not required (but suggested) technically. It may be required in your case!
Keep in mind that a running Zammad instance keeps changing data which may be an issue during long backup runs
Hint
😖 Having trouble backing up?
Have a look at the troubleshooting section to address your issues.
Restore¶
Before Starting¶
Hint
♻️ Migrating from host to host…?
Please refer Migrate Zammad to New Host for more additional information about migrating Zammad.
Warning
Before restoring your backup, please note the following:
The restoration process stops & restarts the Zammad service. This means you usually have to run the restoration script as
root
user.This is mandatory for package installations
On Source code installations, this does not work because of different environments - you could load it beforehand as root user to have access to Zammad specific commands.
If both approaches above do not fit for your case, consider adjusting the backup and restore scripts to your need in an independent directory. You’re working out of script and documentation scope!
PostgreSQL based installations will drop and re-create the database! MySQL / MariaDB based installations restore on the existing database.
You require at least twice the backed up Zammad instance size of free storage. If you have the dump only, factor 3 could be a good number.
Restore¶
- Step 1: Copy your backup files to a fitting location (if needed)
This basically is a given usually if you run a normal restore. Ensure that the user you’re using for restoration is allowed to read the backup files - writing is required for
/opt/zammad/
.The Zammad backup consists of two files. This is their format:
<timestamp>_zammad_db.psql.gz <timestamp>_zammad_files.tar.gz
There’s also two symlinks in your backup directory showing to the newest backup created.
- Step 2: Configure the backup script (if needed)
- On new installation it’s required. For restoration this mainly affects the backup file location.Please consult Backup configuration for more.
- Step 3: Run the restore
Restoration works in two possible ways, depending on how interactive you want to go.
Warning
Restoring old backups may overwrite your
database.yml
. You can find out if that’s the case by having a look into the file.tar.gz
within theconfig
directory. If you can see adatabase.yml
there, ensure to save the original version before restoring.If you found the trap already, you can try the Database Helper: (re)set password.
$ /opt/zammad/contrib/backup/zammad_restore.sh
Provide the requested information to the script wait for the restoration to finish. Depending on the size of your backup and host performance this may take some time.
Warning
Only use the following option if you know what you’re doing! The following command will overwrite existing data without further prompts!
# When called with a timestamp argument (matching the backups filename), # Zammad will proceed immediately to restoring the specified backup. $ /opt/zammad/contrib/backup/zammad_restore.sh 20170507121848
The restore operation should look like this:
# Zammad restore started - Fri Jan 21 17:54:13 CET 2022! The restore will delete your current database! Be sure to have a backup available! Please ensure to have twice the storage of the uncompressed backup size! Note that the restoration USUALLY requires root permissions as services are stopped! Enter 'yes' if you want to proceed! Restore?: yes Enter file date to restore: 20220120124714 20220121175344 File date: 20220121175344 Enter db date to restore: 20220120124714 20220121175344 DB date: 20220121175344 # Stopping Zammad # Checking requirements # ... Dropping current database zammad Dropped database 'zammad' # ... Creating database zammad for owner zammad CREATE DATABASE # Restoring PostgreSQL DB # Restoring Files # Ensuring correct file permissions ... # Clearing Cache ... # Starting Zammad # Zammad restored successfully - Fri Jan 21 17:54:34 CET 2022!
Sample backup process.
- Step 4: Re-install Zammad if restoring a full filesystem restore
Zammads backup scripts backup the whole filesystem of Zammad. This is mainly for backward compatibility but not a hard requirement.
If your filesystem dump contains attachments only (the tar will contain a
storage
folder only) skip this step!For a better overview, please see: step 9 of our migration path.
- Step 5: Apply missing environmental settings
Note
This does not apply to Docker images, as the following settings should be applied upon every start automatically.
If you’ve set any environmental settings like higher web concurrency due to required 🎛️ Performance Tuning, please re-apply your settings now.
If not already done, please install Elasticsearch now (if you want to use it). Follow Step 3: Connect Zammad to reconfigure your installation for Elasticsearch use and rebuild the search index.
You are now ready to continue your work. The rebuild of your search index can safely run during your work, but will cause a degraded search performance and may lead to temporarily not found data.
Migrate Zammad to New Host¶
This is a proof of concept, not a full how to. Your environment may be different. Please note that the steps described on this page are an addition to backing up and restoring. They’re not meant to stand alone - we’ll link and note this at the relevant parts.
If anything goes wrong, please consult the Zammad Community or consider paid support options.
Hint
Migrating from Zammad SaaS? Skip to step 7. For restoration, you’ve received an attachment dump! 🤓
Warning
Restoration & Migration on docker based installation may differ. While the steps are the same on most parts, it is not covered by this documentation!
- Step 1: Note down your environmental adjustments
This mainly affects performance tuning settings. This will be important after restoring.
- Step 2: Install Zammad on the destination host
For the easiest restoration path possible, please install the same version like your origin instance. You could also consider updating the old instance before migrating.
- Choose between these installation types:
- Step 3: Activate maintenance mode
This ends agents and customers sessions. Learn more about the maintenance mode in Zammad.
- Step 4: Disable your communication channels
This is just a safety measurement. As our restore scripts starts Zammad automatically, this may help if something is not in a correct state.
- Step 5: Stop and disable Zammad
Make sure to no longer have Zammad change data before backing up.
$ systemctl disable zammad $ systemctl stop zammad
- Step 6: Backup!
Follow our documentation part for backup creation.
Remember if you’ve created a full filesystem dump or only backed up your attachments. This will be important for the restoration.
If you want to go with the easiest way, consider only dumping your attachments. Learn more on our configuration page.
- Step 7: Transfer your backup files
You’ll find the backup location within the
config
file in the backup directory. Make sure to adjust the backup configuration on the destination host according to our configuration page to provide the correct backup file directory.Provide the file location you transferred the backup files to.
- Step 8: Restore your backup
Follow the steps 1 to 3 of our restoration page to restore the backup on the new host.
If you’re running a source code installation, we recommend install the same version beforehand. This reduces environment fiddling a lot.
Warning
Restoring old backups may overwrite your
database.yml
. You can find out if that’s the case by having a look into the file.tar.gz
within theconfig
directory. If you can see adatabase.yml
there, ensure to save the original version before restoring.If you found the trap already, you can try the Database Helper: (re)set password.
- Important
Stop Zammad after the restoration has finished.
If you experience issues during restoration, please consult Troubleshooting Backup & Restore.
- Step 9: Run required maintenance tasks after restoring
After successful restoration, please continue below depending if you’ve only backed up your attachments or had a full filesystem dump.
Note
Keep in mind that docker-compose and source code installations do not know
zammad run
. Below commands show the package installation way, just remove allzammad run
parts from the commands and run them.This means:
zammad run rails c
would berails c
.- Step 9.1: Clear the cache
$ zammad run rails r "Rails.cache.clear"
Tip
Skip steps 9.1, and 9.2, and 9.3 if you do not have the last possible Zammad version installed. However, make sure to run the next steps in the following order: step 12, then step 10, then step 11.
Note
This step is only needed, if one of the following points is met:
The source and destination Zammad versions are not the same
The Zammad installation is not a source code installation
The Zammad backup is not an export from our hosted setup
Full dumps for source code installations are not covered, however, basically the same below applies to you: You have to ensure that the environments and application files are overwritten with the new / correct version.
Zammad files are distribution and version specific!
- Step 9.1: Uninstall and reinstall Zammad without resolving dependencies
- Debian, Ubuntu
$ dpkg -r --force-depends zammad $ apt install zammad
- OpenSUSE
$ zypper remove -R zammad $ zypper install zammad
Hint
You’re unsure if above is really required and a mere reinstall would be enough? If you run a dedicated install command on for Zammad and receive the following, you absolutely have to run above to fix your installation.
$ root@zammad:/# apt-get update && apt install zammad Reading package lists... Done Building dependency tree Reading state information... Done zammad is already the newest version (x.x.x-xxxxxx.xxxxxx.xxx). 0 upgraded, 0 newly installed, 0 to remove and 0 not upgraded.
- Step 9.2: Clear the cache
Note
Keep in mind that docker-compose and source code installations do not know
zammad run
. Below commands show the package installation way, just remove allzammad run
parts from the commands and run them.This means:
zammad run rails c
would berails c
.$ zammad run rails r "Rails.cache.clear"
- Step 9.3: Ensure Zammad is running
$ systemctl status zammad # If Zammad is not running, run below $ systemctl start zammad
- Step 10: Apply missing environmental settings
If you’ve set any environmental settings like higher web concurrency due to required 🎛️ Performance Tuning, please re-apply your settings now.
If not already done, please install Elasticsearch now (if you want to use it). Follow Step 3: Connect Zammad to reconfigure your installation for Elasticsearch use and rebuild the search index.
You are now ready to continue your work. The rebuild of your search index can safely run during your work, but will cause a degraded search performance and may lead to temporarily not found data.
- Step 11: Re-enable Channels and deactivate maintenance mode
Set the previous deactivated channels back to active if you’re sure everything was successful. At this point Zammad will start to change data!
After verifying the functionality of your channels, allow your agents and customers back in by disabling the maintenance mode.
Learn more about the maintenance mode in Zammad.
Hint
Migrated from Zammad SaaS or switching providers?
Please make sure that your notification and FQDN configuration is still correct. Other wise you may have unexpected issues like not receiving notifications or non functional authentications (3rd party).
- Step 12 (optional): Update Zammad to latest possible version
In case the backup source was not on the latest possible version, please update your Zammad installation now.
In case your installed version is fairly old, please note the upgrade path notes on our updating zammad page.
Troubleshooting Backup & Restore¶
If you encountered errors, they possibly can be corrected.
Exit Codes¶
Our backup & restore scripts come with exit codes to help you finding a solution. However, we do not guarantee a complete error handling.
Beside the exit codes, there are also error messages returned to standard out.
Code |
Description / Situation |
---|---|
|
The script finished successfully (or the error is not handled). |
|
This is a general error. Most often used for script aborts due to incorrect information provided or information missing. |
|
There was an error with database handling. This usually either happens if your database server does not meet script requirements, login data being invalid or „broken‟ database dumps. |
|
There were issues with file / folder permissions. |
Classics¶
Here’s some classics you may encounter.
password authentication failed
orpeer authentication failed
This indicates that the password of your Zammad DB user is either different from your
database.yml
or the wrong database server may be contacted.- „But my Zammad instance is running, how can it be wrong?‟
Zammad may fall back to socket connection which is why you didn’t notice.
- What to do …
Ensure that the provided user credentials are correct. You can also consider to use the Database Helper: (re)set password script, you can find in the backup directory.
Ident authentication failed for user
This indicates your database server does require
ident
authentication. That authentication method is not supported by our scripts.- What to do …
Check
pg_hba.conf
of your PostgreSQL-Server and adjust it if needed.Usually authentication can be allowed like so:
# THIS IS A SAMPLE AND MAY NOT FIT YOUR ENVIRONMENT host all all 127.0.0.1/32 md5 host all all ::1/128 md5
Please consult the offical PostgreSQL documentation for this, as this is out of our documentation scope.
WARNING: You don't seem to have any attachments in the file system!
This indicate you’ve set
FULL_FS_DUMP
tono
but your instance currently does not save attachments to file system.This warning will be shown once before creating an empty directory to allow the backup process to continue successfully.
If you believe that this is an error, please see Storage Settings. In case the issue posts, please consult the Zammad Community.
Helper scripts¶
Danger
☠️ The following scripts are potentially destructive ☠️
You should never run scrips which scopes you don’t understand. Below scripts potentially can make things worse which is why you should evaluate them before hand.
You’re running these scripts at your own risk.
If we found a script is helping you more than 30 lines of new documentation, we may have added a helper script.
Database Helper: (re)set password¶
- Limitations
This script is working for PostgreSQL installations only.
Only local database servers are supported (script changes user).
This script requires to be run as
root
or similar privileged user!- Scopes
Mostly the following installation types will be affected / relevant:
package installations (especially CentOS & SUSE)
possibly source code installations
- Functionality
The script will do the following actions depending on the situation automatically for you. It will double tab by asking for your confirmation up front.
If
database.yml
contains an empty password line, a new password will be generated, and set for the database user of Zammad, and saved to the configuration file.If
database.yml
contains a password, it will be used to set the password of the Zammad database user.Please note that the script will automatically stop and start Zammad!
- Usage
Run
/opt/zammad/contrib/backup/zammad_db_user_helper.sh
and follow the instructions. No specific configurations are required.If errors occur the script will try to bring Zammad back online before exitting. Please ensure that your service is running.
Before you continue, please also note the listed limitations to save your precious time.
- 🔨 Adjust script settings
Learn more about configuration options for backup and restore to see scopes better.
- 🗃️ Create Backups
How to create full dumps of your Zammad installation.
- 🗄️ Restore Backups
Update went wrong and you need to go back? How to restore your instance on a new or the same host.
- 🔀 Migrating to new hosts
This is a general summary on how to best migrate Zammad from host to host. We’ll reference to backup creation and restoration as needed.
- 🔥 Troubleshooting
Things hit the fan? This page might help you out of that pit.
- 🤝 Helper scripts
These scripts may be helpful if Backup & Restore does not work as expected. However note that these are potentially destructive.
Limitations¶
Please note the following limitations which may affect script functionality or availability.
Restoration via script on docker and source code based installations may not work and is out of scope of this documentation as of now
Backup & Restore is only available for PostgreSQL and MySQL / MariaDB like installations
Starting with Zammad 5.0 the scripts require user & password authentication. This is supported by most of our installation types
Backup & Restore is always a full dump of everything (no incrementals)
Restoring or backing up specific information (e.g. Tickets, Users, …) is not supported
Switching / Converting database installations is not possibleRefer this guide to manually switch databases: Migrate to PostgreSQL serverEnvironmental settings (like e.g. Configuration via Environment Variables) are not backed up and thus require you to manually set them on a new host
Restoration into a older Zammad version is not possible nor supported
Do not attempt to restore backup files from custom scripts with the provided scripts by Zammad. This is most likely subject to fail or bring issues you may discover too late.
Configuration via Environment Variables¶
Use these environment variables to configure Zammad’s behavior at runtime.
Note
🙋 What’s an environment variable, and how do I “use” it?
Unfortunately, that question has a very long answer that goes beyond the scope of this article. How you set environment variables will depend on how you installed Zammad (e.g., source, package, or Docker).
But for package installations, here’s a short answer:
# set OPTION to "value"
$ zammad config:set OPTION=value
$ systemctl restart zammad
# get OPTION
$ zammad config:get OPTION
# unset OPTION
$ zammad config:unset OPTION
$ systemctl restart zammad
To learn more, do some googling on environment variables and the shell environment (or execution environment) in Unix.
Important
While below options and remarks affect all installation types of Zammad, please note that environment variables mentioned may be named different for installations based on docker-compose and kubernetes.
General Options¶
- APP_RESTART_CMD
The command Zammad will use to automatically restart the server after changes have been made in the Object Manager. (E.g.,
"systemctl restart zammad"
)If this is undefined, you will have to restart manually after making changes in the Object Manager. Please keep in mind that Zammad runs as unprivileged user. This means that you have to allow the Zammad user via e.g.
sudoers
to run the required restart command.Default: unset
- GPG_PATH
Defines the path to the GPG installation. This is only needed if you installed Zammad from Source, if you want to use different versions of PGP on your machine or if your PGP installation differs from the standard installation.
Default: unset
- RAILS_LOG_TO_STDOUT
Print output directly to standard output instead of
/var/log/zammad/production.log
.This setting can be overwritten during update on package installations. Use
enabled
to turn this option on only until the next update. Usetrue
to turn it on permanently.Default: unset
- ZAMMAD_SAFE_MODE
Ignore availability of third-party services when running Zammad commands. Possible values:
1
ortrue
Warning
Be careful when running Zammad commands on production systems in safe mode.
While it may allow an escape hatch for certain commands, it has a potential to break regular Zammad operations.
Default: unset
- ZAMMAD_HTTP_TYPE
Defines the HTTP protocol of your instance. Possible values:
http
orhttps
Default:
http
- ZAMMAD_FQDN
Defines the fully qualified domain name of the system.
Default:
zammad.example.com
🖧 Network Options¶
Note
Remember to update your web server config to reflect any changes you make here.
- ZAMMAD_BIND_IP
The IP address that the web server is bound to.
Default:
127.0.0.1
- ZAMMAD_RAILS_PORT
The port that the web server is exposed on.
Default:
3000
- ZAMMAD_WEBSOCKET_PORT
The port that the web socket server is exposed on.
Default:
6042
🎛️ Performance Tuning¶
Each of below settings comes with its own tradeoffs.
There are no “recommended values” here; the optimal configuration will depend on your system’s resources and typical application load.
Proceed with caution; when adjusting any of these settings, there is a point at which performance will begin to degrade rather than improve, or other problems will begin to crop up.
Below settings may consume all available database connections. Please consider the database server configuration section for more.
To find out how many users are currently on Zammad, you can use the rails command below:
$ zammad run rails r "p Sessions.list.uniq.count"
- WEB_CONCURRENCY
How many instances of the application server to keep open at a time.
Increasing this can reduce loading times when too many users are on Zammad at once.
Default: unset
- ZAMMAD_SESSION_JOBS_CONCURRENT
How many instances of the session worker to run at a time.
Increasing this can speed up background jobs (like the scheduler) when too many users are on Zammad at once.
Generally speaking, it should only be useful to adjust this setting if you have more than 40 active users at a time.
Warning
🥵 Session workers can be extremely CPU-intensive.
In some cases, they can reach 100% CPU utilization on their own. Increasing this setting is safer on systems with more cores.
Default: unset
- ZAMMAD_PROCESS_SCHEDULED_JOBS_WORKERS
Allows spawning an independent process just for processing scheduled jobs like LDAP syncs. This can free up Zammads background worker for other tasks when running tasks that require fairly long.
Default: unsetMaximum number of workers:1
Danger
Disable processing of scheduled jobs by setting
ZAMMAD_PROCESS_SCHEDULED_JOBS_DISABLE
.Doing so on productive instances will draw important parts of your instance not working. WE STRONGLY encourage against using this flag.
- ZAMMAD_PROCESS_DELAYED_JOBS_WORKERS
How many processes should work on delayed jobs?
Increasing this can improve issues with delayed jobs stacking up in your system. You may want to try to use
ZAMMAD_SESSION_JOBS_CONCURRENT
before though.Default: unsetMaximum number of workers:16
Warning
🥵 This option can be very CPU-intensive.
Danger
Disable processing of delayed jobs by setting
ZAMMAD_PROCESS_DELAYED_JOBS_DISABLE
.Doing so on productive instances will draw important parts of your instance not working. WE STRONGLY encourage against using this flag.
Note
The options listed below allow you to distribute Zammad processes over several application nodes. Even if that’s not your goal, they may provide great benefits on bigger installations.
Please note that distribution of processes on several nodes is out of the scope of this documentation for various reasons.
- REDIS_URL
- Store your web socket connection information within Redis.To do so, tell Zammad where to find your Redis instance:
redis://your.redis.server:6379
If not provided, Zammad falls back to file system (
/opt/zammad/tmp/websocket_*
).Default: unset
- MEMCACHE_SERVERS
- Store your application cache files within Memcached.To do so, tell Zammad where to find your Memcached instance:
your.memcached.server:11211
If not provided, Zammad falls back to file system (
/opt/zammad/tmp/cache*
).Memcached allows you to restrict the maximum size Zammad may store as cache. This comes in handy in terms of performance and keeping caching files small.
1 GB
should be a reasonable size.
Storage Options¶
- S3_URL
Allows you to provide your S3 configuration. Please have a look in our admin documentation, where the setup of S3 storage is described.
Format / example:
https://key:secret@s3.eu-central-1.amazonaws.com/zammad-storage-bucket?region=eu-central-1&force_path_style=true
Configure Database server¶
Note
Parts of this page also applies to both supported database servers. We can’t provide a complete how to and will only enlighten the relevant parts for Zammad.
Within database.yml
(config/
directory) you can define the allowed pool size.
By default each Zammad process takes up to 50
connections (pool: 50
).
This should be fairly enough for every use case. If you experience database connection timeouts or similar pool errors, this usually indicates to other issues that are relevant to your PostgreSQL.
Note
Below only affects PostgreSQL-Servers. All relevant steps for MySQL are mentioned on Software because they’re relevant before installation.
Below you can the locations of the relevant PostgreSQL configuration files to adjust. Keep in mind that versions may differ from your setup - adapt where needed.
/etc/postgresql/{your version}/main/postgresql.conf
/var/lib/pgsql/data/postgresql.conf
Can’t find your configuration files? You can run the following command to get the path:
$ sudo -u postgres psql -c 'SHOW config_file'
- Adjust
max_connections
(mandatory) Zammad will take up to 200 connections by default, with below command you can raise this limit fairly high.
# Raise maximum allowed number of connections $ sed -i "/max_connections/c\max_connections = 2000" <postgresql-configuration-file> # Apply changes by restarting postgresql and Zammad (in this order) $ systemctl restart postgresql zammad
- Adjust PostgreSQL for bigger instances (optional)
Warning
Check below settings first and ensure your system is able to provide the requirements! Below settings are what we found to be useful, everything else is out of scope of this documentation!
# Caching improvements $ sed -i "/shared_buffers/c\shared_buffers = 2GB" <postgresql-configuration-file> $ sed -i "/temp_buffers/c\temp_buffers = 256MB" <postgresql-configuration-file> $ sed -i "/work_mem/c\work_mem = 10MB" <postgresql-configuration-file> $ sed -i "/max_stack_depth/c\max_stack_depth = 5MB" <postgresql-configuration-file> # Apply changes by restarting postgresql and Zammad (in this order) $ systemctl restart postgresql zammad
Migrate to PostgreSQL server¶
Support for MySQL/MariaDB will be dropped in Zammad 7.0 upwards. Make sure to migrate your existing instance of Zammad to PostgreSQL.
The following guide will provide you with a rough direction through that migration process.
Note
🤓 Zammad version requirement ahead
Below commands will only work with Zammad 5.3.0 or higher. Please make sure to update the latest (MySQL supported) version first.
Warning
Proof of concept ahead
As the technical details may differ from system to system, this guide comes without any warranty. Please proceed at your own risk. In doubt please refer to the documentation of the tools used.
Preparation¶
Stop Zammad:
$ systemctl stop zammad
Create a backup of your instance.
Install PostgreSQL¶
$ apt update
$ apt install postgresql postgresql-contrib
$ systemctl start postgresql
$ systemctl enable postgresql
# CentOS 8
$ yum install postgresql-server postgresql-contrib
$ postgresql-setup initdb
$ systemctl start postgresql
$ systemctl enable postgresql
$ zypper refresh
$ zypper install postgresql postgresql-server postgresql-contrib
# openSuSE 15 also requires:
$ zypper install postgresql-server-devel
$ systemctl start postgresql
$ systemctl enable postgresql
Please also have a look at Configure Database server.
Nothing to do, continue with the next step. 🎉
- Install PostgreSQL Dependencies
$ apt install libpq-dev
# CentOS 8 $ yum install postgresql-libs postgresql-devel
$ zypper install postgresql-devel
- Install Gems for Zammad
$ su - zammad $ bundle config set without "test development mysql" $ bundle install
Install pgloader¶
$ apt update
$ apt install pgloader
$ yum install -y pgloader
$ zypper refresh
$ zypper install pgloader
Create pgloader command file¶
Create a command file for pgloader with:
$ zammad run rake zammad:db:pgloader > /tmp/pgloader-command
$ su - zammad
$ rake zammad:db:pgloader > /tmp/pgloader-command
Afterwards, you need to tweak the created file with the correct URL of the target PostgreSQL server.
-- Adjust the PostgreSQL URL below to correct value before executing this command file.
INTO pgsql://zammad:pgsql_password@localhost/zammad
You will at least need to replace psql_password
placeholder in the provided
example.
Verify the rest of the MySQL credentials in the command file, they should reflect the configuration of your current environment.
Database Credentials¶
Adjust the configuration file to fill in the credentials for your new
PostgreSQL server. Use postgresql
as adapter
.
Tip
🤓 For easiest usage …
If you provide your Zammad user with database creation permission, you can
run db:create
in the following section. If you don’t want that, you’ll
have to create the database manually.
Create empty database¶
Now you need to create an empty database in PostgreSQL.
$ zammad run rake db:create
$ su - zammad
$ rake db:create
Migrate¶
You can check your configuration by running pgloader in a dry run first:
$ pgloader --dry-run /tmp/pgloader-command
Once you are ready and setup you can start the actual migration:
$ pgloader --verbose /tmp/pgloader-command
Finishing¶
After the migration has completed, you’ll better clear some cache files:
$ zammad run rails r 'Rails.cache.clear'
$ systemctl start zammad
$ su - zammad
$ rails r 'Rails.cache.clear'
# Run as root
$ systemctl start zammad
Privacy & Data Retention¶
How long does Zammad hold onto user data? How can I manage its user data retention behavior?
On-Premises Data¶
The following kinds of data are stored locally on the production system:
- Tickets and users
By default, Zammad never automatically deletes tickets or users.
To enable automatic deletion of tickets after a given interval, use the scheduler.
To manually delete users and all their associated tickets (e.g. in compliance with a “Right to Forget” request under the GDPR), you can use the data privacy functions in the admin panel or use the console.
- Chat sessions
Once a chat session has been marked closed, it is scheduled for automatic deletion 12 months later.
IP address logs for chat sessions can be manually deleted by following the directions here.
- CTI caller log
The caller log shows only the 60 most recent entries. Each entry in the caller log is automatically deleted after 12 months.
- Log files
Zammad writes log files to disk (typically under
/opt/zammad/log/
).Package installations will set up a separate system utility called
logrotate
to rename and archive (or rotate) log files on a nightly basis and remove old logs after 14 days.If installing from source, it is strongly recommended to configure
logrotate
or a similar log management utility; Zammad will not purge old logs on its own.- User sessions
Zammad maintains session information about every user currently logged in.
This information is automatically purged when a user logs out, and can be viewed or manually deleted via the admin panel (under System → Sessions). Users may also delete their own session information via the user preferences menu, under Device.
Session information includes IP address (and possibly geographic location), browser, time of original login, and time of last visit.
- Data Privacy Tasks
Each entry in the data privacy task list is automatically deleted after 12 months.
External Services¶
Zammad utilizes third-party web services for certain functions, meaning that user data may occasionally be sent or exposed to third parties. These functions can be individually disabled in the admin panel under Settings → System → Services.
Note
By default, the third-party services that Zammad relies on are mostly ones hosted and managed by the Zammad Foundation itself, but Zammad can be extended to interface with other services instead.
The source code for these third-party service integrations can be found here.
- Images
No private images or personally-identifying information are stored on images.zammad.com.
The Images service caches publicly-available images from sources like Gravatar and serves them to the Zammad application as user avatars and organization logos. These images are discovered using MD5 digests of user email addresses and organization domain names. User avatars are cached for 7 days; organization logos are cached for 30 days.
- GeoCalendar
No user information is stored or cached on geo.zammad.com.
As part of its service-level agreement (SLA) functionality, Zammad requires detailed, localized calendar information (e.g., to set the time zone and accommodate national holidays and daylight savings time). The GeoCalendar service is used to retrieve this information.
- GeoIP
No user information is stored or cached on geo.zammad.com.
One of Zammad’s security features is to track user sessions based on the user’s browser and country of origin. Suspicious login activity from a different browser or country may trigger Zammad to dispatch an alert email to the affected user. The GeoIP service is used to associate IP addresses to a geographic origin.
- Geolocation
Since Zammad’s geolocation service relies on Google’s Geocoding API, its use is subject to the Google Privacy Policy.
Zammad uses geolocation to associate tickets with locations to support map-style ticket overviews, which display tickets as points on a map rather than items in a list.
Troubleshooting and FAQ¶
This guide will discuss frequently asked questions and how to resolve common problems with Zammad.
Note
🤓 Troubleshooting unsuccessful or issue not described?
If you can’t solve your issue using the provided troubleshooting steps or can’t find your particular issue described here, feel free to ask the community for technical assistance.
Data missing from the Web-UI / Search data missing or incomplete¶
A commonly reported issue is data missing from the Web-UI. This could be tickets, articles, users or anything else indexed by Elasticsearch and can be caused by missing or incomplete indexes.
If you are experiencing this issue and installed Elasticsearch according to Set up Elasticsearch, please follow these steps to make sure Elasticsearch is working correctly.
- Step 1: Verify Elasticsearch is running
# check elasticsearch status $ systemctl status elasticsearch
This should output something like the following, make sure it says
Active: active (running)
:● elasticsearch.service - Elasticsearch Loaded: loaded (/lib/systemd/system/elasticsearch.service; enabled; vendor preset: enabled) Active: active (running) since Tue 2021-07-20 09:38:21 UTC; 1h 4min ago Docs: https://www.elastic.co Main PID: 1790 (java)
Otherwise, try starting it and check again:
# restart elasticsearch and check status $ systemctl restart elasticsearch $ systemctl status elasticsearch
Warning
If this fails, your Elasticsearch installation is probably broken.Try completely purging and reinstalling Elasticsearch according to Set up Elasticsearch- Step 2: Verify the ingest-attachment plugin is installed correctly
# list installed elasticsearch plugins $ /usr/share/elasticsearch/bin/elasticsearch-plugin list
The output should include
ingest-attachment
.Otherwise, try reinstalling the
ingest-attachment
plugin and check again:$ /usr/share/elasticsearch/bin/elasticsearch-plugin remove ingest-attachment $ /usr/share/elasticsearch/bin/elasticsearch-plugin install ingest-attachment $ systemctl restart elasticsearch $ /usr/share/elasticsearch/bin/elasticsearch-plugin list
- Step 3: Verify Zammad can access Elasticsearch and rebuild the indexes
# force zammad to drop and rebuild the elasticsearch indexes $ zammad run rake zammad:searchindex:rebuild
Optionally, you can specify a number of CPU cores which are used for rebuilding the searchindex, as in the following example with 8 cores:
$ zammad run rake zammad:searchindex:rebuild[8]This should start rebuilding the indexes and output it’s progress:
Dropping indexes... done. Deleting pipeline... done. Creating indexes... done. Creating pipeline... done. Reloading data... - Chat::Session... done in 0 seconds. - Cti::Log... done in 0 seconds. [...]Depending on the system performance and amount of data, this can take a while to complete. Please let this task finish completely and wait until it drops you back to the console.
If this fails or throws an error, there might be something else wrong with your installation. Make sure you followed the complete Elasticsearch set up and integration procedure according to Set up Elasticsearch.
Tip
In many situations where you’re not successful with above steps,
you may want to check Elasticsearch’s log file:
/var/log/elasticsearch/elasticsearch.log
.
Single Sign-On for Kerberos¶
This guide will discuss how to set up single sign-on using Microsoft Active Directory.
Note
SSO can only be configured on self-hosted installations.

As of Zammad 3.5, enabling SSO adds a new button to the sign-in page.¶
Conceptual Overview¶
Like every other web application out there, Zammad has its own logic for signing users up, storing their passwords, authenticating them, and managing their sessions.
If your IT department keeps its own user identity store (like Active Directory), Zammad’s SSO support allows you to leverage that existing auth system so that anyone with an account on your local intranet will 1) automatically have an account in Zammad and 2) be able to log in with a single click.
Note
If you don’t have this IT infrastructure but still want one-click login, see Third-Party Authentication for alternatives.
How does it work?¶
Once enabled, single sign-on activates an endpoint
at https://your.zammad.host/auth/sso
.
When the Zammad server receives a GET request at this endpoint
with a valid username in any one of the following:
an
X-Forwarded-User
request headera
REMOTE_USER
web server environment variablean
HTTP_REMOTE_USER
web server environment variable
it creates a new session for that user.
Note
😬 Wait. SSO allows you to sign in with only a username?
In principle, yes.
How is that okay?
In this guide, we configure our web server (Apache)
to intercept all requests to the /auth/sso
endpoint.
Instead of forwarding them to Zammad,
Apache initiates a three-sided login process (Kerberos authentication)
between the itself, the user, and the Active Directory server.
If Active Directory doesn’t recognize the user or their password, Zammad never sees the request, and the session is never created.
What does this all mean?
It means there are many ways you could set up SSO—you don’t need to follow this guide or even use Active Directory or Kerberos—but if you don’t know what you’re doing, you’re going to end up with a massive security hole.
Getting Started¶
Hint
😵 Too busy to handle it on your own?
We’ve got you covered. Our experts offer custom-tailored workshops to get your team up and running fast and with confidence. Just drop us a line!
You will need:
a Microsoft Active Directory environment with
root access
support for AES 256-bit encryption
a Zammad host with
root access
a fully-qualified domain name (FQDN)
some familiarity with system administration (e.g., Apache configuration)
For best results, set up LDAP integration to make sure your Active Directory and Zammad user accounts are always in sync.
Step 1: Configure Active Directory¶
In the Kerberos authentication scheme, the authentication server (Active Directory) needs to maintain shared secrets with the service (Zammad). To make this possible, we need to register a service principal name (SPN) for Zammad on Active Directory.
Note
These directions have been confirmed on Windows Server 2016.
1a. Create a service account¶
You may use an existing service account if you have one. Admin privileges are not required; a normal user account will do.

Select “This account supports Kerberos AES 256 bit encryption” under Properties > Account > Account options.¶
1b. Register an SPN for Zammad¶
Replace the following placeholders in the command below:
<zammad-host>
Zammad FQDN
<service-acct>
Service account logon name
<password>
Password of the service account (Option
/pass *
did prove to not work)<domain>
Windows domain
<master-domain-controller>
Master domain controller IP/FQD
Below command will prompt for the users password:
$ setspn -s HTTP/<zammad-host> <service-acct>
$ ktpass /princ HTTP/<zammad-host>@<DOMAIN> \
/mapuser <service-acct> \
/crypto AES256-SHA1 \
/ptype KRB5_NT_PRINCIPAL \
/pass <password> -SetPass +DumpSalt \
/target <master-domain-controller> \
/out zammad.keytab
1c. Note the secret key and version number¶
The output of the command above contains important data for Step 2e below:
Using legacy password setting method
Failed to set property 'servicePrincipalName' to 'HTTP/<zammad-host>' on Dn 'CN=Zammad Service,DC=<domain>,DC=<tld>': 0x13.
WARNING: Unable to set SPN mapping data.
If <service-acct> already has an SPN mapping installed for HTTP/<zammad-host>, this is no cause for concern.
Building salt with principalname HTTP/<zammad-host> and domain <domain> (encryption type 18)...
Hashing password with salt "<domain><service-acct>".
Key created.
Output keytab to zammad.keytab:
Keytab version: 0x502
keysize 67 <service-acct>@<domain> ptype 1 (KRB5_NT_PRINCIPAL) vno 3 etype 0x12 (AES256-SHA1) keylength 32 (0x5ee827c30c736dd4095c9cbe146eabc216415b1ddb134db6aabd61be8fdf7fb1)
On the last line, take note of:
- the secret key
in parentheses at the end (0x5ee827…)
- the secret key version number
preceded by
vno
(3)
Step 2: Remove NGINX, Set up Apache + Kerberos¶
Next, the Zammad host must be configured to support Kerberos (and to accept auth credentials provided by the Active Directory server).
In most cases, you would have to recompile NGINX from source with an extra module to enable Kerberos support. To get around this, we will use Apache, which offers Kerberos support through a plug-in module instead.
Note
All commands in this section must be run as root (or with sudo
).
2a. Turn off NGINX¶
Warning
This will take your Zammad instance offline until Apache is fully configured and running.
$ systemctl stop nginx # turn off nginx
$ systemctl disable nginx # keep it off after reboot
If you wish to minimize downtime, you can save this step for last; just bear in mind that Apache will not start if the port it wants to listen on is being used by NGINX.
If for any reason you can’t complete this tutorial, simply turn off Apache and restore NGINX:
$ systemctl stop apache2
$ systemctl disable apache2
$ systemctl enable nginx
$ systemctl start nginx
2b. Pre-Configure Apache¶
This documentation expects a already working Apache configuration. Please see Configure the webserver before continuing.
2c. Install further Apache dependencies¶
$ apt update
$ apt install krb5-user libapache2-mod-auth-kerb
$ yum install krb5-workstation mod_auth_kerb
$ zypper ref
$ zypper install krb5-client apache2-mod_auth_kerb
2d. Enable Apache modules¶
SSO requires modules that are not enabled by default. By default you can use
a2enmod
to do so.
$ a2enmod auth_kerb rewrite
$ systemctl restart apache2
add/uncomment the appropriate LoadModule
statements
in your Apache config:
# /etc/httpd/conf/httpd.conf
LoadModule auth_kerb_module /usr/lib/apache2/modules/mod_auth_kerb.so
LoadModule rewrite_module modules/mod_rewrite.so
2e. Configure Kerberos¶
Kerberos realm configuration is how you tell the Zammad server how to reach the domain controller (Active Directory server).
Replace the following placeholders in the sample config below:
<domain>
Windows domain
<domain-controller>
Domain controller IP/FQDN(s)
<master-domain-controller>
Master domain controller IP/FQDN
(must not be read-only, but can be the same as
<domain-controller>
)
# /etc/krb5.conf
[libdefaults]
default_realm = <DOMAIN>
default_tkt_enctypes = aes256-cts-hmac-sha1-96
default_tgs_enctypes = aes256-cts-hmac-sha1-96
permitted_enctypes = aes256-cts-hmac-sha1-96
kdc_timesync = 1
ccache_type = 4
forwardable = false
proxiable = false
fcc-mit-ticketflags = false
[realms]
# multiple KDCs ok (one `kdc = ...` definition per line)
<DOMAIN> = {
kdc = <domain-controller>
admin_server = <master-domain-controller>
default_domain = <domain>
}
[domain_realm]
.<domain> = <DOMAIN>
<domain> = <DOMAIN>
2f. Generate keytab¶
Apache needs a Kerberos keytab (key table) to manage its shared secrets with the domain controller.
Replace the following placeholders in the commands below:
<zammad-host>
Zammad FQDN
<domain>
Windows domain
<secret-key>
Secret key (omit the leading
0x
)<vno>
Secret key version number
The secret key and version number were found in Step 1: Configure Active Directory above.
$ ktutil
ktutil: addent -key -p HTTP/<zammad-host>@<DOMAIN> -k <vno> -e aes256-cts
Key for HTTP/<zammad-host>@<domain> (hex): <secret-key>
ktutil: list # confirm the entry was added successfully
slot KVNO Principal
---- ---- ---------------------------------------------------------------
1 3 HTTP/<zammad-host>@<DOMAIN>
ktutil: wkt /root/zammad.keytab # write keytab to disk
ktutil: quit
Then, place the keytab in the Apache config directory and set the appropriate permissions:
$ mv /root/zammad.keytab /etc/apache2/
$ chown www-data:www-data /etc/apache2/zammad.keytab
$ chmod 400 /etc/apache2/zammad.keytab
$ mv /root/zammad.keytab /etc/httpd/
$ chown apache:apache /etc/httpd/zammad.keytab
$ chmod 400 /etc/httpd/zammad.keytab
2g. Configure Apache¶
Add the following directive to the end of the virtual host configuration file
to create your Kerberos SSO endpoint at /auth/sso
:
Replace the following placeholders in the command below:
<zammad-host>
Zammad FQDN
<domain>
Windows domain
The configuration for CentOS and OpenSUSE below contains two Krb5KeyTab
lines! Keep only the one you need.
<LocationMatch "/auth/sso">
SSLRequireSSL
AuthType Kerberos
AuthName "Your Zammad"
KrbMethodNegotiate On
KrbVerifyKDC On
KrbMethodK5Passwd On
KrbAuthRealms <DOMAIN>
KrbLocalUserMapping on # strips @REALM suffix from REMOTE_USER variable
KrbServiceName HTTP/<zammad-host>@<DOMAIN>
Krb5KeyTab /etc/apache2/zammad.keytab # Ubuntu, Debian, & openSUSE
Krb5KeyTab /etc/httpd/zammad.keytab # CentOS
require valid-user
RewriteEngine On
RewriteCond %{LA-U:REMOTE_USER} (.+)
RewriteRule . - [E=RU:%1,NS]
RequestHeader set X-Forwarded-User "%{RU}e" env=RU
</LocationMatch>
2g. Restart Apache to apply changes¶
$ systemctl restart apache2
Step 3: Enable SSO in Zammad¶
Next, enable “Authencation via SSO” in Zammad’s Admin Panel under Settings > Security > Third-Party Applications:

In Zammad 3.5, this option adds a Sign in using SSO button to the sign-in page.¶
Note
On older versions of Zammad,
visit https://your.zammad.host/auth/sso
to sign in.
Step 4: Configure Client System (Windows Only)¶
For the full SSO experience (i.e., for passwordless, one-click sign-in), Zammad users must:
be on the Active Directory server’s local intranet; and
modify their network settings for the Zammad host to be treated as a local intranet server.

Without this step, users must enter their Active Directory credentials during SSO.¶
Tip
This setting can be centrally managed across the entire intranet using a group policy object (GPO).
Add your Zammad FQDN in Internet Options under Security > Local Intranet > Sites > Advanced.
Select “Require server verification (https:) for all sites in this zone”.
Under Security level for this zone > Custom level… > Settings > User Authentication > Logon, select “Automatic logon only in Intranet Zone”.

Note
This option cannot be centrally managed because it is set in the browser rather than Windows Settings.
Enter
about:config
in the address bar. Click Accept the risk and continue.Search for the
network.negotiate-auth.trusted-uris
option.Double-click to edit, then add your Zammad FQDN.
Restart Firefox to apply your changes.

Enter about:config
in the address bar to access advanced
settings in Firefox.¶
Troubleshooting¶
Are all relevant FQDNs/hostnames reachable from your Active Directory and Zammad servers (including each other’s)?
Are the system clocks of your Active Directory and Zammad servers synchronized within five minutes of each other? (Kerberos is a time-sensitive protocol.)
Errors in Apache Logs¶
Tip
Try raising your Apache log level temporarily.
Add LogLevel debug
to your virtual host configuration,
then restart the service to apply the changes.
- “an unsupported mechanism was requested”
Does your Active Directory service account have Kerberos AES 256-bit encryption enabled?
If for some reason your server does not support AES 256-bit encryption, the LDAP Wiki has more information about Kerberos encryption types.
- “failed to verify krb5 credentials: Key version is not available”
Did you use the exact version number (
vno
) provided byktpass
when generating your keytab?Try generating it again, just to be sure.
- “unspecified GSS failure. Minor code may provide more information (, No key table entry found for HTTP/FQDN@DOMAIN)”
Does the service name you provided to
setspn
exactly match the one you used when generating your keytab?Try generating it again, just to be sure.
- “No key table entry found for HTTP/FQDN@DOMAIN”
Does your virtual host configuration’s
KrbServiceName
setting exactly match the service name you provided tosetspn
?This setting is case-sensitive.
- “Warning: received token seems to be NTLM, which isn’t supported by the Kerberos module. Check your IE configuration”
Is your Zammad host accessible at an FQDN? This error may indicate that you configured your Zammad host as a numeric IP address instead.
- “Cannot decrypt ticket for HTTP/FQDN@DOMAIN”
Did you make sure to change the password on your Active Directory service account after enabling 256-bit AES encryption?
And did you make sure to register the SPN (with
ktpass
) and generate your keytab (withktutil
) after changing your password?Try running
kinit -k -t <path to keytab> HTTP/<zammad-host>@<DOMAIN>
. If no output is returned, you’re good - if you see “kinit: Preauthentication failed while getting initial credentials” your credentials provided were wrong or you used/pass *
during ktpass command.- “failed when verifying KDC” and “failed to verify krb5 credentials: Decrypt integrity check failed”
Ensure
KrbServiceName
is the correct ServiceName provided via setspn.Ensure your Active Directory supports the encryption method configured.
If all above is correct and the rest of FAQ also is ensured, make sure your client does not cache the results.
klist purge
clears the clients cache - a reboot of your client would do too.
Reporting Tools (Third party)¶
This guide will discuss how to set up third party reporting tools with Zammad.

Use third party reporting tools to boost your reporting capabilities.¶
Note
🚧 Limitations 🚧
Please note that this guide expects all requirements to be up and running. We will not cover core configurations of each tool. Please also note that we can’t support you with configuration of your specific third party tool.
🤓 Specific use cases
You may have specific use cases which we can’t cover in this documentation. The following sub pages and also our List of Indexed Attributes should provide enough information to help you!
Getting Started¶
You will need
A instance of the reporting tool of your choice (hosted or self-hosted)
(read) access to your Elasticsearch index
Warning
Never expose Elasticsearch to the public if you’re not sure how to do it. Especially never without authentication! Zammad stores very sensitive information within the Elasticsearch Index.
a Zammad instance (version 4 or above) that supports your use case
Third Party Reporting Tools known to be working¶
Grafana¶
Grafana allows you to query, visualize and alert on metrics your Zammad installation stores within the Elasticsearch indexes.
Note
🚧 Limitations 🚧
Please note that this guide expects all requirements to be up and running. We will not cover core configurations of each tool. Please also note that we can’t support you with configuration of your specific third party tool.
🤓 Specific use cases
You may have specific use cases which we can’t cover in this documentation. The following sub pages and also our List of Indexed Attributes should provide enough information to help you!
Overview¶
- Quickly jump to…
- You will need
A Grafana 10.3+ instance (hosted or self hosted)
(read) access to your Elasticsearch index
Warning
Never expose Elasticsearch to the public if you’re not sure how to do it. Especially never without authentication! Zammad stores very sensitive information within the Elasticsearch Index.
a Zammad instance (version 4 or above) that supports your use case
Setting up required data sources¶
Hint
🤓 You may not need all data sources
Before we start: The data sources always follow the same scheme. We reduced
below information to name
, time field name
and index name
.
Everything else relies on your environment and is out of our scope.
Note
Please replace
zammad_production_
with your fitting prefix.
- ES - Chat Sessions:
- Index name:
zammad_production_chat_session
Time field name:created_at
- ES - CTI Log:
- Index name:
zammad_production_cti_log
Time field name:start_at
- ES - Ticket Articles:
- Index name:
zammad_production_ticket
Time field name:article.created_at
- ES - Tickets by closed_at:
- Index name:
zammad_production_ticket
Time field name:close_at
- ES - Tickets by created_at:
- Index name:
zammad_production_ticket
Time field name:created_at
- ES - Tickets by first_response_at:
- Index name:
zammad_production_ticket
Time field name:first_response_at
With above data sources you basically have everything you need to start building your own dashboards. 🎉
Tip
🤓 Not sure about your index names?
Querying your Elasticsearch like below
# Replace localhost:9200 with the IP/URL of your setup if needed $ curl http://localhost:9200/_aliases?pretty=truewill return a list that looks similar to the following:
{ "zammad_production_knowledge_base_translation" : { "aliases" : { } }, "zammad_production_ticket_priority" : { "aliases" : { } }, "zammad_production_stats_store" : { "aliases" : { } }, "zammad_production_organization" : { "aliases" : { } }, "zammad_production_cti_log" : { "aliases" : { } }, "zammad_production_group" : { "aliases" : { } }, "zammad_production_knowledge_base_answer_translation" : { "aliases" : { } }, "zammad_production_ticket" : { "aliases" : { } }, "zammad_production_ticket_state" : { "aliases" : { } }, "zammad_production_chat_session" : { "aliases" : { } }, "zammad_production_user" : { "aliases" : { } }, "zammad_production_knowledge_base_category_translation" : { "aliases" : { } } }
The Dashboards¶
If you want to get inspired, you can also use our sample dashboards as mentioned below. These dashboards can also be found on GitHub.
Importing an existing Dashboard¶
Navigate to ➕ → Import and either upload the json file you received or use the grafana.com ID. During importing you can provide a dashboard name and folder. You’ll also be asked to map the data sources to your environment. If you used our data source names above, you can simply search for the same name.
![]()
Importing existing dashboards by ID¶
Ticket statistics¶
- This dashboard provides graphs for:
- It also contains specific ticket and article meta information:
- Required data sources:
ES - Ticket Articles
ES - Tickets by created_at
ES - Tickets by closed_at
- 1(1,2)
Specific reference IDs are not the same on every instance and thus the panel may not work or show incorrect data. Check the panels description on how to find our the relations on your system.
- 2(1,2,3,4,5,6,7)
Some values are not available as time series information. This means we can only display the last value of the field in question.
- 3(1,2)
Requires SLA function to be active. Negative values indicate SLA violations.
Chat-Session statistics¶
- This dashboard provides graphs for:
Chat session creations
- It also contains specific chat session meta information:
top 10
chat tags
chat agents
chat exit pages
city origins
chat topic ratio
average number of messages within chat-sessions
average chatting time
World map with chat origin countries
- Required data sources:
ES - Chat Sessions
CTI-Log statistics¶
- This dashboard provides graphs for:
number of calls per direction (in / out)
- It also contains specific chat session meta information:
call ratio (in / out)
average waiting time
average talking time
top 10
callers (in)
call answerers (in)
- Required data sources:
ES - CTI Log
Note
Want to use another tool?
Don’t worry, if it does support Elasticsearch Indexes, you may be good to go! See List of Indexed Attributes for available indexes.