Need a custom OTOBO REST API operation?
Softoft develops Generic Interface operations, secure webservices, OTOBO packages, tests, deployment, and handover documentation.
In this guide: Extend the OTOBO Generic Interface with secure custom REST operations, using the OpenTicketAIConnector catalogue endpoints as a practical package pattern.
Related: OTOBO REST API · Web Services · Plugin development
The standard OTOBO REST API covers common ticket operations such as TicketCreate, TicketGet, and TicketSearch. A custom integration may also need endpoints for queue metadata, Dynamic Field values, configuration data, or domain-specific actions. Those endpoints belong in a packaged OTOBO Generic Interface extension—not in a YAML mapping alone.
Need a custom OTOBO REST API operation?
Softoft develops Generic Interface operations, secure webservices, OTOBO packages, tests, deployment, and handover documentation.
Book a 15-minute introductory call if you already know which data or action your endpoint must expose.
A custom REST surface consists of four connected layers:
| Layer | What you add | When it runs |
|---|---|---|
| Operation | Kernel/GenericInterface/Operation/<Controller>/<Name>.pm | Every request |
| Registration | GenericInterface::Operation::Module###… in SysConfig XML | Configuration reload |
| Webservice | Provider operation and RouteOperationMapping in YAML | REST request routing |
| Package | .sopm file list and install/upgrade hooks | Package lifecycle |
flowchart LR client[HTTPClient] --> gi["Generic Interface HTTP REST"] gi --> route[WebserviceRoute] route --> operation[PerlOperation] operation --> core["OTOBO Kernel System APIs"] operation --> response[JSONResponse]
Skip one layer and the operation either does not appear in Admin → Web Services, returns a 404, or disappears after the next package installation.
The examples below use TicketAICatalog::QueueList, a real pattern from OpenTicketAIConnector. Replace the controller, operation name, user, and webservice name with values owned by your package.
Kernel::GenericInterface::Operation::<Controller>::<Name>Kernel/GenericInterface/Operation/<Controller>/<Name>.pm<Controller>::<Name>Operation classes inherit from Kernel::GenericInterface::Operation::Common, which provides Auth and ReturnError. A shared base class is useful once multiple operations need the same authentication and validation.
package Kernel::GenericInterface::Operation::TicketAICatalog::Base;
use strict;use warnings;use parent qw(Kernel::GenericInterface::Operation::Common);
our $ObjectManagerDisabled = 1;
sub new { my ( $Type, %Param ) = @_; my $Self = {}; bless $Self, $Type;
for my $Needed (qw(DebuggerObject WebserviceID)) { return if !$Param{$Needed}; $Self->{$Needed} = $Param{$Needed}; } return $Self;}
sub _AuthOrError { my ( $Self, %Param ) = @_; my ( $UserID, $UserType ) = $Self->Auth(%Param); return ( $UserID, undef ) if $UserID;
return ( undef, $Self->ReturnError( ErrorCode => 'TicketAICatalog.AuthFail', ErrorMessage => 'Authentication failed!', ), );}
1;$ObjectManagerDisabled = 1 matters because Generic Interface operation objects are created outside the normal object-manager factory path.
RunUse OTOBO kernel APIs through $Kernel::OM. Keep request validation and response shapes explicit.
package Kernel::GenericInterface::Operation::TicketAICatalog::QueueList;
use strict;use warnings;use parent qw(Kernel::GenericInterface::Operation::TicketAICatalog::Base);
our $ObjectManagerDisabled = 1;
sub Run { my ( $Self, %Param ) = @_;
my ( $UserID, $Error ) = $Self->_AuthOrError(%Param); return $Error if $Error;
my $QueueObject = $Kernel::OM->Get('Kernel::System::Queue'); my %Queues = $QueueObject->QueueList( Valid => 0 ); my @Items;
for my $QueueID ( sort { $a <=> $b } keys %Queues ) { my %Queue = $QueueObject->QueueGet( ID => $QueueID ); next if !%Queue; push @Items, { ID => $QueueID + 0, Name => $Queue{Name} // $Queues{$QueueID}, Comment => $Queue{Comment} // '', Valid => ( ( $Queue{ValidID} // 1 ) == 1 ) ? 1 : 0, }; }
return { Success => 1, Data => { Item => \@Items } };}
1;Read decoded JSON or query parameters from $Param{Data}. Credentials remain sibling keys consumed by Auth.
my $Data = $Param{Data} || {};my $Name = $Data->{Name} // '';
return $Self->ReturnError( ErrorCode => 'TicketAICatalog.MissingName', ErrorMessage => 'Name is required.',) if !$Name;Prefer idempotent, read-only operations. Put mutations behind explicit routes, validate every field, and restrict them to a dedicated API user.
OTOBO discovers operation modules through settings named:
GenericInterface::Operation::Module###<Controller>::<Name>Register each type in an XML file under Kernel/Config/Files/XML/:
<?xml version="1.0" encoding="utf-8"?><otobo_config version="2.0" init="Application"> <Setting Name="GenericInterface::Operation::Module###TicketAICatalog::QueueList" Required="0" Valid="1"> <Description Translatable="1">Catalogue: list queues.</Description> <Navigation>GenericInterface::Operation::ModuleRegistration</Navigation> <Value> <Hash> <Item Key="Name">QueueList</Item> <Item Key="Controller">TicketAICatalog</Item> <Item Key="ConfigDialog">AdminGenericInterfaceOperationDefault</Item> </Hash> </Value> </Setting></otobo_config>After installation and configuration rebuild, TicketAICatalog::QueueList should appear as an operation type in the webservice administration screen.
Registration makes the backend selectable. The webservice YAML exposes it over HTTP:
Provider: Operation: queue-list: Type: TicketAICatalog::QueueList Description: Lists queues with ID, name, comment, and validity. MappingInbound: Type: Simple Config: KeyMapDefault: MapTo: "" MapType: Keep ValueMap: UserLogin: ValueMapRegEx: .*: custom-api-user MappingOutbound: Type: Simple Config: KeyMapDefault: MapTo: "" MapType: Keep
Transport: Type: HTTP::REST Config: MaxLength: "1000000" RouteOperationMapping: queue-list: Route: /queue-list RequestMethod: - GET - POSTThe operation key under Provider.Operation must exactly match the key under RouteOperationMapping. An inbound login rewrite can force every request to use a restricted API agent, but it does not replace HTTPS, a strong password, group permissions, input validation, and network controls.
Every Perl, XML, and YAML file must be listed in the .sopm; otherwise it never reaches the target system.
<Filelist> <File Permission="644" Location="Kernel/GenericInterface/Operation/TicketAICatalog/Base.pm"/> <File Permission="644" Location="Kernel/GenericInterface/Operation/TicketAICatalog/QueueList.pm"/> <File Permission="644" Location="Kernel/Config/Files/XML/MyConnector.xml"/> <File Permission="644" Location="var/webservices/examples/MyConnector.yml"/></Filelist>Use package lifecycle hooks to create the restricted API user and import or update the packaged YAML:
<CodeInstall Type="post"><![CDATA[ $Kernel::OM->Get('Kernel::System::MyConnector::Setup')->Install();]]></CodeInstall><CodeReinstall Type="post"><![CDATA[ $Kernel::OM->Get('Kernel::System::MyConnector::Setup')->Install();]]></CodeReinstall><CodeUpgrade Type="post"><![CDATA[ $Kernel::OM->Get('Kernel::System::MyConnector::Setup')->Install();]]></CodeUpgrade><CodeUninstall Type="pre"><![CDATA[ $Kernel::OM->Get('Kernel::System::MyConnector::Setup')->Uninstall();]]></CodeUninstall>On upgrades, update the database webservice from the packaged YAML. This ensures new routes arrive without manual admin clicks.
The final URL has this shape:
https://helpdesk.example/otobo/nph-genericinterface.pl/Webservice/MyConnector/queue-listSmoke-test authentication, method, route, and JSON:
curl -sS -u 'custom-api-user:API_PASSWORD' \ -X POST \ 'https://helpdesk.example/otobo/nph-genericinterface.pl/Webservice/MyConnector/queue-list'Test success and stable errors, unauthorized access, invalid input, empty results, large responses, and package upgrades. Client code should depend on documented response contracts rather than internal Perl classes.
.pm with new, Run, authentication, validation, and a stable Data shape.GenericInterface::Operation::Module###<Controller>::<Name> in SysConfig XML..sopm and bump the package version.RouteOperationMapping.Item: Check Valid filters and the API user’s group and queue permissions.WebserviceUpdate.Custom operations are production code inside your ticket system. They need secure authorization, stable contracts, package lifecycle handling, tests, and an upgrade strategy—not just a working Perl module.
Let Softoft build and maintain your OTOBO REST extension
From requirements and operation design through package delivery, deployment, documentation, and maintenance.
Book a 15-minute call to discuss the endpoints, connected system, and deployment environment.
Yes. Add a Generic Interface operation module, register it in SysConfig, map it to a REST route, and ship all files in an OTOBO package.
The operation is usually missing its GenericInterface::Operation::Module SysConfig registration, or the package and configuration were not rebuilt after installation.
Yes. Softoft offers custom Generic Interface operation, webservice, package, testing, deployment, and maintenance services.