1# BEGIN BPS TAGGED BLOCK {{{
2#
3# COPYRIGHT:
4#
5# This software is Copyright (c) 1996-2021 Best Practical Solutions, LLC
6#                                          <sales@bestpractical.com>
7#
8# (Except where explicitly superseded by other copyright notices)
9#
10#
11# LICENSE:
12#
13# This work is made available to you under the terms of Version 2 of
14# the GNU General Public License. A copy of that license should have
15# been provided with this software, but in any event can be snarfed
16# from www.gnu.org.
17#
18# This work is distributed in the hope that it will be useful, but
19# WITHOUT ANY WARRANTY; without even the implied warranty of
20# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
21# General Public License for more details.
22#
23# You should have received a copy of the GNU General Public License
24# along with this program; if not, write to the Free Software
25# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
26# 02110-1301 or visit their web page on the internet at
27# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html.
28#
29#
30# CONTRIBUTION SUBMISSION POLICY:
31#
32# (The following paragraph is not intended to limit the rights granted
33# to you to modify and distribute this software under the terms of
34# the GNU General Public License and is only of importance to you if
35# you choose to contribute your changes and enhancements to the
36# community by submitting them to Best Practical Solutions, LLC.)
37#
38# By intentionally submitting any modifications, corrections or
39# derivatives to this work, or any other work intended for use with
40# Request Tracker, to Best Practical Solutions, LLC, you confirm that
41# you are the copyright holder for those contributions and you grant
42# Best Practical Solutions,  LLC a nonexclusive, worldwide, irrevocable,
43# royalty-free, perpetual, license to use, copy, create derivative
44# works based on those contributions, and sublicense and distribute
45# those contributions and any derivatives thereof.
46#
47# END BPS TAGGED BLOCK }}}
48
49use strict;
50use warnings;
51use 5.010001;
52
53package RT::REST2;
54
55our $REST_PATH = '/REST/2.0';
56
57use Plack::Builder;
58use RT::REST2::Dispatcher;
59
60=encoding utf-8
61
62=head1 NAME
63
64RT::REST2 - RT REST API v. 2.0 under /REST/2.0/
65
66=head1 USAGE
67
68=head2 Tutorial
69
70To make it easier to authenticate to REST2, we recommend also using
71L<RT::Authen::Token>. Visit "Logged in as ___" -> Settings -> Auth
72Tokens. Create an Auth Token, give it any description (such as "REST2
73with curl"). Make note of the authentication token it provides to you.
74
75For other authentication options see the section
76L<Authentication Methods> below.
77
78=head3 Authentication
79
80Run the following in a terminal, filling in XX_TOKEN_XX from the auth
81token above and XX_RT_URL_XX with the URL for your RT instance.
82
83    curl -H 'Authorization: token XX_TOKEN_XX' 'XX_RT_URL_XX/REST/2.0/queues/all'
84
85This does an authenticated request (using the C<Authorization> HTTP
86header with type C<token>) for all of the queues you can see. You should
87see a response, typical of search results, like this:
88
89    {
90       "total" : 1,
91       "count" : 1,
92       "page" : 1,
93       "pages" : 1,
94       "per_page" : 20,
95       "items" : [
96          {
97             "type" : "queue",
98             "id" : "1",
99             "_url" : "XX_RT_URL_XX/REST/2.0/queue/1"
100          }
101       ]
102    }
103
104This format is JSON, which is a format for which many programming languages
105provide libraries for parsing and generating.
106
107(If you instead see a response like C<{"message":"Unauthorized"}> that
108indicates RT couldn't process your authentication token successfully;
109make sure the word "token" appears between "Authorization:" and the auth
110token that RT provided to you)
111
112=head3 Following Links
113
114You can request one of the provided C<_url>s to get more information
115about that queue.
116
117    curl -H 'Authorization: token XX_TOKEN_XX' 'XX_QUEUE_URL_XX'
118
119This will give a lot of information, like so:
120
121    {
122       "id" : 1,
123       "Name" : "General",
124       "Description" : "The default queue",
125       "Lifecycle" : "default",
126       ...
127       "CustomFields" : {},
128       "_hyperlinks" : [
129          {
130             "id" : "1",
131             "ref" : "self",
132             "type" : "queue",
133             "_url" : "XX_RT_URL_XX/REST/2.0/queue/1"
134          },
135          {
136             "ref" : "history",
137             "_url" : "XX_RT_URL_XX/REST/2.0/queue/1/history"
138          },
139          {
140             "ref" : "create",
141             "type" : "ticket",
142             "_url" : "XX_RT_URL_XX/REST/2.0/ticket?Queue=1"
143          }
144       ],
145    }
146
147Of particular note is the C<_hyperlinks> key, which gives you a list of
148related resources to examine (following the
149L<https://en.wikipedia.org/wiki/HATEOAS> principle). For example an
150entry with a C<ref> of C<history> lets you examine the transaction log
151for a record. You can implement your REST API client knowing that any
152other hypermedia link with a C<ref> of C<history> has the same meaning,
153regardless of whether it's the history of a queue, ticket, asset, etc.
154
155Another C<ref> you'll see in C<_hyperlinks> is C<create>, with a C<type>
156of C<ticket>. This of course gives you the URL to create tickets
157I<in this queue>. Importantly, if your user does I<not> have the
158C<CreateTicket> permission in this queue, then REST2 would simply not
159include this hyperlink in its response to your request. This allows you
160to dynamically adapt your client's behavior to its presence or absence,
161just like the web version of RT does.
162
163=head3 Creating Tickets
164
165Let's use the C<_url> from the C<create> hyperlink with type C<ticket>.
166
167To create a ticket is a bit more involved, since it requires providing a
168different HTTP verb (C<POST> instead of C<GET>), a C<Content-Type>
169header (to tell REST2 that your content is JSON instead of, say, XML),
170and the fields for your new ticket such as Subject. Here is the curl
171invocation, wrapped to multiple lines for readability.
172
173    curl -X POST
174         -H "Content-Type: application/json"
175         -d '{ "Subject": "hello world" }'
176         -H 'Authorization: token XX_TOKEN_XX'
177            'XX_TICKET_CREATE_URL_XX'
178
179If successful, that will provide output like so:
180
181    {
182        "_url" : "XX_RT_URL_XX/REST/2.0/ticket/20",
183        "type" : "ticket",
184        "id"   : "20"
185    }
186
187(REST2 also produces the status code of C<201 Created> with a C<Location>
188header of the new ticket, which you may choose to use instead of the
189JSON response)
190
191We can fetch that C<_url> to continue working with this newly-created
192ticket. Request the ticket like so (make sure to include the C<-i> flag
193to see response's HTTP headers).
194
195    curl -i -H 'Authorization: token XX_TOKEN_XX' 'XX_TICKET_URL_XX'
196
197You'll first see that there are many hyperlinks for tickets, including
198one for each Lifecycle action you can perform, history, comment,
199correspond, etc. Again these adapt to whether you have the appropriate
200permissions to do these actions.
201
202Additionally you'll see an C<ETag> header for this record, which can be
203used for conflict avoidance
204(L<https://en.wikipedia.org/wiki/HTTP_ETag>). We'll first try updating this
205ticket with an I<invalid> C<ETag> to see what happens.
206
207=head3 Updating Tickets
208
209For updating tickets we use the C<PUT> verb, but otherwise it looks much
210like a ticket creation.
211
212    curl -X PUT
213         -H "Content-Type: application/json"
214         -H "If-Match: invalid-etag"
215         -d '{ "Subject": "trial update" }'
216         -H 'Authorization: token XX_TOKEN_XX'
217            'XX_TICKET_URL_XX'
218
219You'll get an error response like C<{"message":"Precondition Failed"}>
220and a status code of 412. If you examine the ticket, you'll also see
221that its Subject was not changed. This is because the C<If-Match> header
222advises the server to make changes I<if and only if> the ticket's
223C<ETag> matches what you provide. Since it differed, the server refused
224the request and made no changes.
225
226Now, try the same request by replacing the value "invalid-etag" in the
227C<If-Match> request header with the real C<ETag> you'd received when you
228requested the ticket previously. You'll then get a JSON response like:
229
230    ["Ticket 1: Subject changed from 'hello world' to 'trial update'"]
231
232which is a list of messages meant for displaying to an end-user.
233
234If you C<GET> the ticket again, you'll observe that the C<ETag>
235header now has a different value, indicating that the ticket itself has
236changed. This means if you were to retry the C<PUT> update with the
237previous (at the time, expected) C<ETag> you would instead be rejected
238by the server with Precondition Failed.
239
240You can use C<ETag> and C<If-Match> headers to avoid race conditions
241such as two people updating a ticket at the same time. Depending on the
242sophistication of your client, you may be able to automatically retry
243the change by incorporating the changes made on the server (for example
244adding time worked can be automatically be recalculated).
245
246You may of course choose to ignore the C<ETag> header and not provide
247C<If-Match> in your requests; RT doesn't require its use.
248
249=head3 Replying/Commenting Tickets
250
251You can reply to or comment a ticket by C<POST>ing to C<_url> from the
252C<correspond> or C<comment> hyperlinks that were returned when fetching the
253ticket.
254
255    curl -X POST
256         -H "Content-Type: application/json"
257         -d '{
258              "Subject"    : "response",
259              "Content"    : "What is your <em>issue</em>?",
260              "ContentType": "text/html",
261              "TimeTaken"  : "1"
262            }'
263         -H 'Authorization: token XX_TOKEN_XX'
264            'XX_TICKET_URL_XX'/correspond
265
266Replying or commenting a ticket is quite similar to a ticket creation: you
267send a C<POST> request, with data encoded in C<JSON>. The difference lies in
268the properties of the JSON data object you can pass:
269
270=over 4
271
272=item C<Subject>
273
274The subject of your response/comment, optional
275
276=item C<Content>
277
278The content of your response/comment, mandatory unless there is a non empty
279C<Attachments> property to add at least one attachment to the ticket (see
280L<Add Attachments> section below).
281
282=item C<ContentType>
283
284The MIME content type of your response/comment, typically C<text/plain> or
285C</text/html>, mandatory unless there is a non empty C<Attachments> property
286to add at least one attachment to the ticket (see L<Add Attachments> section
287below).
288
289=item C<TimeTaken>
290
291The time, in minutes, you've taken to work on your response/comment, optional.
292
293=item C<Status>
294
295The new status (for example, "open", "rejected", etc.) to set the
296ticket to.  The Status value must be a valid status based on the
297lifecycle of the ticket's current queue.
298
299=item C<CustomRoles>
300
301A hash whose keys are custom role names and values are as described below:
302
303For a single-value custom role, the value must be a string representing an
304email address or user name; the custom role is set to the user with
305that email address or user name.
306
307For a multi-value custom role, the value can be a string representing
308an email address or user name, or can be an array of email addresses
309or user names; in either case, the members of the custom role are set
310to the corresponding users.
311
312=item C<CustomFields>
313
314A hash similar to the C<CustomRoles> hash, but whose keys are custom
315field names that apply to the Ticket; those fields are set to the
316supplied values.
317
318=item C<TxnCustomFields>
319
320A hash similar to the C<CustomRoles> hash, but whose keys are custom
321field names that apply to the Transaction; those fields are set
322to the supplied values.
323
324=back
325
326=head3 Add Attachments
327
328You can attach any binary or text file to a ticket via create, correspond, or
329comment by adding an C<Attachments> property in the JSON object. The value
330should be a JSON array where each item represents a file you want to attach.
331Each item is a JSON object with the following properties:
332
333=over 4
334
335=item C<FileName>
336
337The name of the file to attach to your response/comment, mandatory.
338
339=item C<FileType>
340
341The MIME type of the file to attach to your response/comment, mandatory.
342
343=item C<FileContent>
344
345The content, I<encoded in C<MIME Base64>> of the file to attach to your
346response/comment, mandatory.
347
348=back
349
350The reason why you should encode the content of any file to C<MIME Base64>
351is that a JSON string value should be a sequence of zero or more Unicode
352characters. C<MIME Base64> is a binary-to-text encoding scheme widely used
353(for eg. by web browser) to send binary data when text data is required.
354Most popular language have C<MIME Base64> libraries that you can use to
355encode the content of your attached files (see L<MIME::Base64> for C<Perl>).
356Note that even text files should be C<MIME Base64> encoded to be passed in
357the C<FileContent> property.
358
359Here's a Perl example to send an image and a plain text file attached to a
360comment:
361
362    #!/usr/bin/perl
363    use strict;
364    use warnings;
365
366    use LWP::UserAgent;
367    use JSON;
368    use MIME::Base64;
369    use Data::Dumper;
370
371    my $url = 'http://rt.local/REST/2.0/ticket/1/comment';
372
373    my $img_path = '/tmp/my_image.png';
374    my $img_content;
375    open my $img_fh, '<', $img_path or die "Cannot read $img_path: $!\n";
376    {
377        local $/;
378        $img_content = <$img_fh>;
379    }
380    close $img_fh;
381    $img_content = MIME::Base64::encode_base64($img_content);
382
383    my $txt_path = '~/.bashrc';
384    my $txt_content;
385    open my $txt_fh, '<', glob($txt_path) or die "Cannot read $txt_path: $!\n";
386    {
387        local $/;
388        $txt_content = <$txt_fh>;
389    }
390    close $txt_fh;
391    $txt_content = MIME::Base64::encode_base64($txt_content);
392
393    my $json = JSON->new->utf8;
394    my $payload = {
395        Content => '<p>I want <b>two</b> <em>attachments</em></p>',
396        ContentType => 'text/html',
397        Subject => 'Attachments in JSON Array',
398        Attachments => [
399            {
400                FileName => 'my_image.png',
401                FileType => 'image/png',
402                FileContent => $img_content,
403            },
404            {
405                FileName => '.bashrc',
406                FileType => 'text/plain',
407                FileContent => $txt_content,
408            },
409        ],
410    };
411
412    my $req = HTTP::Request->new(POST => $url);
413    $req->header('Authorization' => 'token 6-66-66666666666666666666666666666666');
414    $req->header('Content-Type'  => 'application/json' );
415    $req->header('Accept'        => 'application/json' );
416    $req->content($json->encode($payload));
417
418    my $ua = LWP::UserAgent->new;
419    my $res = $ua->request($req);
420    print Dumper($json->decode($res->content)) . "\n";
421
422Encoding the content of attachments file in C<MIME Base64> has the drawback
423of adding some processing overhead and to increase the sent data size by
424around 33%. RT's REST2 API provides another way to attach any binary or text
425file to your response or comment by C<POST>ing, instead of a JSON request, a
426C<multipart/form-data> request. This kind of request is similar to what the
427browser sends when you add attachments in RT's reply or comment form. As its
428name suggests, a C<multipart/form-data> request message contains a series of
429parts, each representing a form field. To reply to or comment a ticket, the
430request has to include a field named C<JSON>, which, as previously, is a
431JSON object with C<Subject>, C<Content>, C<ContentType>, C<TimeTaken>
432properties. Files can then be attached by specifying a field named
433C<Attachments> for each of them, with the content of the file as value and
434the appropriate MIME type.
435
436The curl invocation is quite straightforward:
437
438    curl -X POST
439         -H "Content-Type: multipart/form-data"
440         -F 'JSON={
441                    "Subject"    : "Attachments in multipart/form-data",
442                    "Content"    : "<p>I want <b>two</b> <em>attachments</em></p>",
443                    "ContentType": "text/html",
444                    "TimeTaken"  : "1"
445                  };type=application/json'
446         -F 'Attachments=@/tmp/my_image.png;type=image/png'
447         -F 'Attachments=@/tmp/.bashrc;type=text/plain'
448         -H 'Authorization: token XX_TOKEN_XX'
449            'XX_TICKET_URL_XX'/comment
450
451=head3 Summary
452
453RT's REST2 API provides the tools you need to build robust and dynamic
454integrations. Tools like C<ETag>/C<If-Match> allow you to avoid
455conflicts such as two people taking a ticket at the same time. Using
456JSON for all data interchange avoids problems caused by parsing text.
457Hypermedia links inform your client application of what the user has the
458ability to do.
459
460Careful readers will see that, other than our initial entry into the
461system, we did not I<generate> any URLs. We only I<followed> links, just
462like you do when browsing a website on your computer. We've better
463decoupled the client's implementation from the server's REST API.
464Additionally, this system lets you be informed of new capabilities in
465the form of additional hyperlinks.
466
467Using these tools and principles, REST2 will help you build rich,
468robust, and powerful integrations with the other applications and
469services that your team uses.
470
471=head2 Endpoints
472
473Currently provided endpoints under C</REST/2.0/> are described below.
474Wherever possible please consider using C<_hyperlinks> hypermedia
475controls available in response bodies rather than hardcoding URLs.
476
477For simplicity, the examples below omit the extra options to
478curl for SSL like --cacert.
479
480=head3 Tickets
481
482    GET /tickets?query=<TicketSQL>
483        search for tickets using TicketSQL
484
485    GET /tickets?simple=1;query=<simple search query>
486        search for tickets using simple search syntax
487
488    # If there are multiple saved searches using the same description, the
489    # behavior of "which saved search shall be selected" is undefined, use
490    # id instead in this case.
491
492    # If both search and other arguments like "query" are specified, the
493    # latter takes higher precedence than the corresponding fields defined
494    # in the given saved search.
495
496    GET /tickets?search=<saved search id or description>
497        search for tickets using saved search
498
499    POST /tickets
500        search for tickets with the 'search' or 'query' and optional 'simple' parameters
501
502    POST /ticket
503        create a ticket; provide JSON content
504
505    GET /ticket/:id
506        retrieve a ticket
507
508    PUT /ticket/:id
509        update a ticket's metadata; provide JSON content
510
511    PUT /ticket/:id/take
512    PUT /ticket/:id/untake
513    PUT /ticket/:id/steal
514        take, untake, or steal the ticket
515
516    DELETE /ticket/:id
517        set status to deleted
518
519    POST /ticket/:id/correspond
520    POST /ticket/:id/comment
521        add a reply or comment to the ticket
522
523    GET /ticket/:id/history
524        retrieve list of transactions for ticket
525
526    POST /tickets/bulk
527        create multiple tickets; provide JSON content(array of hashes)
528
529    PUT /tickets/bulk
530        update multiple tickets' metadata; provide JSON content(array of hashes)
531
532    POST /tickets/bulk/correspond
533    POST /tickets/bulk/comment
534        add a reply or comment to multiple tickets; provide JSON content(array of hashes)
535
536=head3 Ticket Examples
537
538Below are some examples using the endpoints above.
539
540    # Create a ticket, setting some custom fields and a custom role
541    curl -X POST -H "Content-Type: application/json" -u 'root:password'
542        -d '{ "Queue": "General", "Subject": "Create ticket test",
543            "Requestor": "user1@example.com", "Cc": "user2@example.com",
544            "CustomRoles": {"My Role": "staff1@example.com"},
545            "Content": "Testing a create",
546            "CustomFields": {"Severity": "Low"}}'
547        'https://myrt.com/REST/2.0/ticket'
548
549    # Update a ticket, with a custom field update
550    curl -X PUT -H "Content-Type: application/json" -u 'root:password'
551        -d '{ "Subject": "Update test", "CustomFields": {"Severity": "High"}}'
552        'https://myrt.com/REST/2.0/ticket/6'
553
554    # Update a ticket, with links update
555    curl -X PUT -H "Content-Type: application/json" -u 'root:password'
556        -d '{ "DependsOn": [2, 3], "ReferredToBy": 1 }'
557        'https://myrt.com/REST/2.0/ticket/6'
558
559    curl -X PUT -H "Content-Type: application/json" -u 'root:password'
560        -d '{ "AddDependsOn": [4, 5], "DeleteReferredToBy": 1 }'
561        'https://myrt.com/REST/2.0/ticket/6'
562
563    # Merge a ticket into another
564    curl -X PUT -H "Content-Type: application/json" -u 'root:password'
565        -d '{ "MergeInto": 3 }'
566        'https://myrt.com/REST/2.0/ticket/6'
567
568    # Take a ticket
569    curl -X PUT -H "Content-Type: application/json" -u 'root:password'
570        'https://myrt.com/REST/2.0/ticket/6/take'
571
572    # Untake a ticket
573    curl -X PUT -H "Content-Type: application/json" -u 'root:password'
574        'https://myrt.com/REST/2.0/ticket/6/untake'
575
576    # Steal a ticket
577    curl -X PUT -H "Content-Type: application/json" -u 'root:password'
578        'https://myrt.com/REST/2.0/ticket/6/steal'
579
580    # Correspond a ticket
581    curl -X POST -H "Content-Type: application/json" -u 'root:password'
582        -d '{ "Content": "Testing a correspondence", "ContentType": "text/plain" }'
583        'https://myrt.com/REST/2.0/ticket/6/correspond'
584
585    # Correspond a ticket with a transaction custom field
586    curl -X POST -H "Content-Type: application/json" -u 'root:password'
587        -d '{ "Content": "Testing a correspondence", "ContentType": "text/plain",
588              "TxnCustomFields": {"MyField": "custom field value"} }'
589        'https://myrt.com/REST/2.0/ticket/6/correspond'
590
591    # Comment on a ticket
592    curl -X POST -H "Content-Type: application/json" -u 'root:password'
593        -d 'Testing a comment'
594        'https://myrt.com/REST/2.0/ticket/6/comment'
595
596    # Comment on a ticket with custom field update
597    curl -X POST -H "Content-Type: application/json" -u 'root:password'
598        -d '{ "Content": "Testing a comment", "ContentType": "text/plain", "CustomFields": {"Severity": "High"} }'
599        'https://myrt.com/REST/2.0/ticket/6/comment'
600
601    # Comment on a ticket with custom role update
602    curl -X POST -H "Content-Type: application/json" -u 'root:password'
603        -d '{ "Content": "Testing a comment", "ContentType": "text/plain", "CustomRoles": {"Manager": "manager@example.com"} }'
604        'https://myrt.com/REST/2.0/ticket/6/comment'
605
606    # Update many tickets at once with bulk by sending an array with ticket ids
607    # Results are returned for each update in a JSON array with ticket ids and corresponding messages
608    curl -X POST -H "Content-Type: application/json" -u 'root:password'
609        -d '[{ "id": "20", "Content": "Testing a correspondence", "ContentType": "text/plain" },
610             { "id": "18", "Content": "Testing a correspondence", "ContentType": "text/plain", "Status":"resolved", "CustomRoles": {"Manager": "manager@example.com"}, "CustomFields": {"State": "New York"} }]'
611        'https://myrt.com/REST/2.0/tickets/bulk/correspond'
612
613    [["20","Correspondence added"],["18","Correspondence added","State New York added","Added manager@example.com as Manager for this ticket","Status changedfrom 'open' to 'resolved'"]]
614
615
616=head3 Ticket Fields
617
618The following describes some of the values you can send when creating and updating
619tickets as shown in the examples above.
620
621=over 4
622
623=item Ticket Links
624
625As shown above, you can update links on a ticket with a C<PUT> and passing the link
626relationship you want to create. The available keys are Parent, Child, RefersTo,
627ReferredToBy, DependsOn, and DependedOnBy. These correspond with the standard link
628types on a ticket. The value can be a single ticket id or an array of ticket ids.
629The indicated link relationship will be set to the value passed, adding or removing
630as needed.
631
632You can specifically add or remove a link by prepending C<Add> or C<Delete> to
633the link type, like C<AddParent> or C<DeleteParent>. These versions also accept
634a single ticket id or an array.
635
636=back
637
638=head3 Transactions
639
640    GET /transactions?query=<TransactionSQL>
641    POST /transactions
642        search for transactions using TransactionSQL
643
644    GET /transactions?query=<JSON>
645    POST /transactions
646        search for transactions using L</JSON searches> syntax
647
648    GET /ticket/:id/history
649    GET /queue/:id/history
650    GET /queue/:name/history
651    GET /asset/:id/history
652    GET /user/:id/history
653    GET /user/:name/history
654    GET /group/:id/history
655        get transactions for record
656
657    GET /transaction/:id
658        retrieve a transaction
659
660=head3 Transactions Examples
661
662    # Search transactions using C<TransactionSQL>
663    curl -X POST -u 'root:password' -d "query=Creator='Dave' AND Type='Correspond'"
664        'https://myrt.com/REST/2.0/transactions'
665
666=head3 Attachments and Messages
667
668    GET /attachments?query=<JSON>
669    POST /attachments
670        search for attachments using L</JSON searches> syntax
671
672    GET /transaction/:id/attachments
673        get attachments for transaction
674
675    GET /ticket/:id/attachments
676        get attachments associated with a ticket
677
678    GET /attachment/:id
679        retrieve an attachment.  Note that the C<Content> field contains
680        the base64-encoded representation of the raw content.
681
682=head3 Image and Binary Object Custom Field Values
683
684    GET /download/cf/:id
685        retrieve an image or a binary file as an object custom field value
686
687=head3 Queues
688
689    GET /queues/all
690        retrieve list of all queues you can see
691
692    GET /queues?query=<JSON>
693    POST /queues
694        search for queues using L</JSON searches> syntax
695
696    POST /queue
697        create a queue; provide JSON content
698
699    GET /queue/:id
700    GET /queue/:name
701        retrieve a queue by numeric id or name
702
703    PUT /queue/:id
704    PUT /queue/:name
705        update a queue's metadata; provide JSON content
706
707    DELETE /queue/:id
708    DELETE /queue/:name
709        disable queue
710
711    GET /queue/:id/history
712    GET /queue/:name/history
713        retrieve list of transactions for queue
714
715=head3 Assets
716
717    GET /assets?query=<AssetSQL>
718    POST /assets
719        search for assets using AssetSQL
720
721    GET /assets?query=<JSON>
722    POST /assets
723        search for assets using L</JSON searches> syntax
724
725    POST /asset
726        create an asset; provide JSON content
727
728    GET /asset/:id
729        retrieve an asset
730
731    PUT /asset/:id
732        update an asset's metadata; provide JSON content
733
734    DELETE /asset/:id
735        set status to deleted
736
737    GET /asset/:id/history
738        retrieve list of transactions for asset
739
740=head3 Assets Examples
741
742Below are some examples using the endpoints above.
743
744    # Create an Asset
745    curl -X POST -H "Content-Type: application/json" -u 'root:password'
746        -d '{"Name" : "Asset From Rest", "Catalog" : "General assets", "Content" : "Some content"}'
747        'https://myrt.com/REST/2.0/asset'
748
749    # Search Assets
750    curl -X POST -H "Content-Type: application/json" -u 'root:password'
751    -d '[{ "field" : "id", "operator" : ">=", "value" : 0 }]'
752    'https://myrt.com/REST/2.0/assets'
753
754    # Search Assets Based On Custom Field Values using L</JSON searches>
755    curl -X POST -H "Content-Type: application/json" -u 'root:password'
756        -d '[{ "field" : "CustomField.{Department}", "value" : "Engineering" }]'
757        'https://myrt.com/REST/2.0/assets'
758
759    # Search assets using AssetSQL
760    curl -X POST -u 'root:password' -d "query=Catalog='General assets' AND 'CF.{Asset Type}' LIKE 'Computer'"
761        'https://myrt.com/REST/2.0/assets'
762
763=head3 Assets Examples
764
765Below are some examples using the endpoints above.
766
767    # Create an Asset
768    curl -X POST -H "Content-Type: application/json" -u 'root:password'
769        -d '{"Name" : "Asset From Rest", "Catalog" : "General assets", "Content" : "Some content"}'
770        'https://myrt.com/REST/2.0/asset'
771
772    # Search Assets
773    curl -X POST -H "Content-Type: application/json" -u 'root:password'
774    -d '[{ "field" : "id", "operator" : ">=", "value" : 0 }]'
775    'https://myrt.com/REST/2.0/assets'
776
777=head3 Catalogs
778
779    GET /catalogs/all
780        retrieve list of all catalogs you can see
781
782    GET /catalogs?query=<JSON>
783    POST /catalogs
784        search for catalogs using L</JSON searches> syntax
785
786    POST /catalog
787        create a catalog; provide JSON content
788
789    GET /catalog/:id
790    GET /catalog/:name
791        retrieve a catalog by numeric id or name
792
793    PUT /catalog/:id
794    PUT /catalog/:name
795        update a catalog's metadata; provide JSON content
796
797    DELETE /catalog/:id
798    DELETE /catalog/:name
799        disable catalog
800
801=head3 Articles
802
803    GET /articles?query=<JSON>
804    POST /articles
805        search for articles using L</JSON searches> syntax
806
807    POST /article
808        create an article; provide JSON content
809
810    GET /article/:id
811        retrieve an article
812
813    PUT /article/:id
814        update an article's metadata; provide JSON content
815
816    DELETE /article/:id
817        set status to deleted
818
819    GET /article/:id/history
820        retrieve list of transactions for article
821
822=head3 Classes
823
824    GET /classes/all
825        retrieve list of all classes you can see
826
827    GET /classes?query=<JSON>
828    POST /classes
829        search for classes using L</JSON searches> syntax
830
831    POST /class
832        create a class; provide JSON content
833
834    GET /class/:id
835    GET /class/:name
836        retrieve a class by numeric id or name
837
838    PUT /class/:id
839    PUT /class/:name
840        update a class's metadata; provide JSON content
841
842    DELETE /class/:id
843    DELETE /class/:name
844        disable class
845
846=head3 Users
847
848    GET /users?query=<JSON>
849    POST /users
850        search for users using L</JSON searches> syntax
851
852    POST /user
853        create a user; provide JSON content
854
855    GET /user/:id
856    GET /user/:name
857        retrieve a user by numeric id or username (including its memberships and whether it is disabled)
858
859    PUT /user/:id
860    PUT /user/:name
861        update a user's metadata (including its Disabled status); provide JSON content
862
863    DELETE /user/:id
864    DELETE /user/:name
865        disable user
866
867    GET /user/:id/history
868    GET /user/:name/history
869        retrieve list of transactions for user
870
871=head3 Groups
872
873    GET /groups?query=<JSON>
874    POST /groups
875        search for groups using L</JSON searches> syntax
876
877    POST /group
878        create a (user defined) group; provide JSON content
879
880    GET /group/:id
881        retrieve a group (including its members and whether it is disabled)
882
883    PUT /group/:id
884        update a groups's metadata (including its Disabled status); provide JSON content
885
886    DELETE /group/:id
887        disable group
888
889    GET /group/:id/history
890        retrieve list of transactions for group
891
892=head3 User Memberships
893
894    GET /user/:id/groups
895    GET /user/:name/groups
896        retrieve list of groups which a user is a member of
897
898    PUT /user/:id/groups
899    PUT /user/:name/groups
900        add a user to groups; provide a JSON array of groups ids
901
902    DELETE /user/:id/group/:id
903    DELETE /user/:name/group/:id
904        remove a user from a group
905
906    DELETE /user/:id/groups
907    DELETE /user/:name/groups
908        remove a user from all groups
909
910=head3 Group Members
911
912    GET /group/:id/members
913        retrieve list of direct members of a group
914
915    GET /group/:id/members?recursively=1
916        retrieve list of direct and recursive members of a group
917
918    GET /group/:id/members?users=0
919        retrieve list of direct group members of a group
920
921    GET /group/:id/members?users=0&recursively=1
922        retrieve list of direct and recursive group members of a group
923
924    GET /group/:id/members?groups=0
925        retrieve list of direct user members of a group
926
927    GET /group/:id/members?groups=0&recursively=1
928        retrieve list of direct and recursive user members of a group
929
930    PUT /group/:id/members
931        add members to a group; provide a JSON array of principal ids
932
933    DELETE /group/:id/member/:id
934        remove a member from a group
935
936    DELETE /group/:id/members
937        remove all members from a group
938
939=head3 Custom Fields
940
941    GET /customfields?query=<JSON>
942    POST /customfields
943        search for custom fields using L</JSON searches> syntax
944
945    POST /customfield
946        create a customfield; provide JSON content
947
948    GET /catalog/:id/customfields?query=<JSON>
949    POST /catalog/:id/customfields
950        search for custom fields attached to a catalog using L</JSON searches> syntax
951
952    GET /class/:id/customfields?query=<JSON>
953    POST /class/:id/customfields
954        search for custom fields attached to a class using L</JSON searches> syntax
955
956    GET /queue/:id/customfields?query=<JSON>
957    POST /queue/:id/customfields
958        search for custom fields attached to a queue using L</JSON searches> syntax
959
960    GET /customfield/:id
961        retrieve a custom field, with values if type is Select
962
963    GET /customfield/:id?category=<category name>
964        retrieve a custom field, with values filtered by category if type is Select
965
966    PUT /customfield/:id
967        update a custom field's metadata; provide JSON content
968
969    DELETE /customfield/:id
970        disable customfield
971
972=head3 Custom Field Values
973
974    GET /customfield/:id/values?query=<JSON>
975    POST /customfield/:id/values
976        search for values of a custom field  using L</JSON searches> syntax
977
978    POST /customfield/:id/value
979        add a value to a custom field; provide JSON content
980
981    GET /customfield/:id/value/:id
982        retrieve a value of a custom field
983
984    PUT /customfield/:id/value/:id
985        update a value of a custom field; provide JSON content
986
987    DELETE /customfield/:id/value/:id
988        remove a value from a custom field
989
990=head3 Custom Roles
991
992    GET /customroles?query=<JSON>
993    POST /customroles
994        search for custom roles using L</JSON searches> syntax
995
996    GET /customrole/:id
997        retrieve a custom role
998
999=head3 Saved Searches
1000
1001    GET /searches?query=<JSON>
1002    POST /searches
1003        search for saved searches using L</JSON searches> syntax
1004
1005    GET /search/:id
1006    GET /search/:description
1007        retrieve a saved search
1008
1009=head3 Miscellaneous
1010
1011    GET /
1012        produces this documentation
1013
1014    GET /rt
1015        produces system information
1016
1017=head2 JSON searches
1018
1019Some resources accept a basic JSON structure as the search conditions which
1020specifies one or more fields to limit on (using specified operators and
1021values).  An example:
1022
1023    curl -si -u user:pass https://rt.example.com/REST/2.0/queues -XPOST --data-binary '
1024        [
1025            { "field":    "Name",
1026              "operator": "LIKE",
1027              "value":    "Engineering" },
1028
1029            { "field":    "Lifecycle",
1030              "value":    "helpdesk" },
1031
1032            { "field"    : "CustomField.{Department}",
1033              "operator" : "=",
1034              "value"    : "Human Resources" }
1035        ]
1036    '
1037
1038The JSON payload must be an array of hashes with the keys C<field> and C<value>
1039and optionally C<operator>.
1040
1041Results can be sorted by using multiple query parameter arguments
1042C<orderby> and C<order>. Each C<orderby> query parameter specify a field
1043to be used for sorting results. If the request includes more than one
1044C<orderby> query parameter, results are sorted according to
1045corresponding fields in the same order than they are specified. For
1046instance, if you want to sort results according to creation date and
1047then by id (in case of some items have the same creation date), your
1048request should specify C<?orderby=Created&orderby=id>. By default,
1049results are sorted in ascending order. To sort results in descending
1050order, you should use C<order=DESC> query parameter. Any other value for
1051C<order> query parameter will be treated as C<order=ASC>, for ascending
1052order. The order of the C<order> query parameters should be the same as
1053the C<orderby> query parameters. Therefore, if you specify two fields to
1054sort the results (with two C<orderby> parameters) and you want to sort
1055the second field by descending order, you should also explicitely
1056specify C<order=ASC> for the first field:
1057C<orderby=Created&order=ASC&orderby=id&order=DESC>. C<orderby> and
1058C<order> query parameters are supported in both JSON and TicketSQL
1059searches.
1060
1061The same C<field> is specified more than one time to express more than one
1062condition on this field. For example:
1063
1064    [
1065        { "field":    "id",
1066          "operator": ">",
1067          "value":    $min },
1068
1069        { "field":     "id",
1070          "operator": "<",
1071          "value":    $max }
1072    ]
1073
1074By default, RT will aggregate these conditions with an C<OR>, except for
1075when searching queues, where an C<AND> is applied. If you want to search for
1076multiple conditions on the same field aggregated with an C<AND> (or an C<OR>
1077for queues), you can specify C<entry_aggregator> keys in corresponding
1078hashes:
1079
1080    [
1081        { "field":    "id",
1082          "operator": ">",
1083          "value":    $min },
1084
1085        { "field":             "id",
1086          "operator":         "<",
1087          "value":            $max,
1088          "entry_aggregator": "AND" }
1089    ]
1090
1091Results are returned in
1092L<the format described below|/"Example of plural resources (collections)">.
1093
1094=head2 Example of plural resources (collections)
1095
1096Resources which represent a collection of other resources use the following
1097standard JSON format:
1098
1099    {
1100       "count" : 20,
1101       "page" : 1,
1102       "pages" : 191,
1103       "per_page" : 20,
1104       "next_page" : "<collection path>?page=2"
1105       "total" : 3810,
1106       "items" : [
1107          { … },
1108          { … },
11091110       ]
1111    }
1112
1113Each item is nearly the same representation used when an individual resource
1114is requested.
1115
1116=head2 Object Custom Field Values
1117
1118When creating (via C<POST>) or updating (via C<PUT>) a resource which has
1119some custom fields attached to, you can specify the value(s) for these
1120customfields in the C<CustomFields> property of the JSON object parameter.
1121The C<CustomFields> property should be a JSON object, with each property
1122being the custom field identifier or name. If the custom field can have only
1123one value, you just have to speciy the value as JSON string for this custom
1124field. If the customfield can have several value, you have to specify a JSON
1125array of each value you want for this custom field.
1126
1127    "CustomFields": {
1128        "XX_SINGLE_CF_ID_XX"   : "My Single Value",
1129        "XX_MULTI_VALUE_CF_ID": [
1130            "My First Value",
1131            "My Second Value"
1132        ]
1133    }
1134
1135Note that for a multi-value custom field, you have to specify all the values
1136for this custom field. Therefore if the customfield for this resource
1137already has some values, the existing values must be including in your
1138update request if you want to keep them (and add some new values).
1139Conversely, if you want to delete some existing values, do not include them
1140in your update request (including only values you wan to keep). The
1141following example deletes "My Second Value" from the previous example:
1142
1143    "CustomFields": {
1144        "XX_MULTI_VALUE_CF_ID": [
1145            "My First Value"
1146        ]
1147    }
1148
1149To delete a single-value custom field, set its value to JSON C<null>
1150(C<undef> in Perl):
1151
1152    "CustomFields": {
1153        "XX_SINGLE_CF_ID_XX" : null
1154    }
1155
1156New values for Image and Binary custom fields can be set by specifying a
1157JSON object as value for the custom field identifier or name with the
1158following properties:
1159
1160=over 4
1161
1162=item C<FileName>
1163
1164The name of the file to attach, mandatory.
1165
1166=item C<FileType>
1167
1168The MIME type of the file to attach, mandatory.
1169
1170=item C<FileContent>
1171
1172The content, I<encoded in C<MIME Base64>> of the file to attach, mandatory.
1173
1174=back
1175
1176The reason why you should encode the content of the image or binary file to
1177C<MIME Base64> is that a JSON string value should be a sequence of zero or
1178more Unicode characters. C<MIME Base64> is a binary-to-text encoding scheme
1179widely used (for eg. by web browser) to send binary data when text data is
1180required. Most popular language have C<MIME Base64> libraries that you can
1181use to encode the content of your attached files (see L<MIME::Base64> for
1182C<Perl>). Note that even text files should be C<MIME Base64> encoded to be
1183passed in the C<FileContent> property.
1184
1185    "CustomFields": {
1186        "XX_SINGLE_IMAGE_OR_BINARY_CF_ID_XX"   : {
1187            "FileName"   : "image.png",
1188            "FileType"   : "image/png",
1189            "FileContent": "XX_BASE_64_STRING_XX"
1190        },
1191        "XX_MULTI_VALUE_IMAGE_OR_BINARY_CF_ID": [
1192            {
1193                "FileName"   : "another_image.png",
1194                "FileType"   : "image/png",
1195                "FileContent": "XX_BASE_64_STRING_XX"
1196            },
1197            {
1198                "FileName"   : "hello_world.txt",
1199                "FileType"   : "text/plain",
1200                "FileContent": "SGVsbG8gV29ybGQh"
1201            }
1202        ]
1203    }
1204
1205Encoding the content of image or binary files in C<MIME Base64> has the
1206drawback of adding some processing overhead and to increase the sent data
1207size by around 33%. RT's REST2 API provides another way to upload image or
1208binary files as custom field alues by sending, instead of a JSON request, a
1209C<multipart/form-data> request. This kind of request is similar to what the
1210browser sends when you upload a file in RT's ticket creation or update
1211forms. As its name suggests, a C<multipart/form-data> request message
1212contains a series of parts, each representing a form field. To create or
1213update a ticket with image or binary file, the C<multipart/form-data>
1214request has to include a field named C<JSON>, which, as previously, is a
1215JSON object with C<Queue>, C<Subject>, C<Content>, C<ContentType>, etc.
1216properties. But instead of specifying each custom field value as a JSON
1217object with C<FileName>, C<FileType> and C<FileContent> properties, each
1218custom field value should be a JSON object with C<UploadField>. You can
1219choose anything you want for this field name, except I<Attachments>, which
1220should be reserved for attaching files to a response or a comment to a
1221ticket. Files can then be attached by specifying a field named as specified
1222in the C<CustomFields> property for each of them, with the content of the
1223file as value and the appropriate MIME type.
1224
1225Here is an exemple of a curl invocation, wrapped to multiple lines for
1226readability, to create a ticket with a multipart/request to upload some
1227image or binary files as custom fields values.
1228
1229    curl -X POST
1230         -H "Content-Type: multipart/form-data"
1231         -F 'JSON={
1232                    "Queue"      : "General",
1233                    "Subject"    : "hello world",
1234                    "Content"    : "That <em>damned</em> printer is out of order <b>again</b>!",
1235                    "ContentType": "text/html",
1236                    "CustomFields"  : {
1237                        "XX_SINGLE_IMAGE_OR_BINARY_CF_ID_XX"   => { "UploadField": "FILE_1" },
1238                        "XX_MULTI_VALUE_IMAGE_OR_BINARY_CF_ID" => [ { "UploadField": "FILE_2" }, { "UploadField": "FILE_3" } ]
1239                    }
1240                  };type=application/json'
1241         -F 'FILE_1=@/tmp/image.png;type=image/png'
1242         -F 'FILE_2=@/tmp/another_image.png;type=image/png'
1243         -F 'FILE_3=@/etc/cups/cupsd.conf;type=text/plain'
1244         -H 'Authorization: token XX_TOKEN_XX'
1245            'XX_RT_URL_XX'/tickets
1246
1247If you want to delete some existing values from a multi-value image or
1248binary custom field, you can just pass the existing filename as value for
1249the custom field identifier or name, no need to upload again the content of
1250the file. The following example will delete the text file and keep the image
1251upload in previous example:
1252
1253    "CustomFields": {
1254        "XX_MULTI_VALUE_IMAGE_OR_BINARY_CF_ID": [
1255                "image.png"
1256        ]
1257    }
1258
1259To download an image or binary file which is the custom field value of a
1260resource, you just have to make a C<GET> request to the entry point returned
1261for the corresponding custom field when fetching this resource, and it will
1262return the content of the file as an octet string:
1263
1264    curl -i -H 'Authorization: token XX_TOKEN_XX' 'XX_TICKET_URL_XX'
1265
1266    {
1267        […]
1268        "XX_IMAGE_OR_BINARY_CF_ID_XX" : [
1269            {
1270                "content_type" : "image/png",
1271                "filename" : "image.png",
1272                "_url" : "XX_RT_URL_XX/REST/2.0/download/cf/XX_IMAGE_OR_BINARY_OCFV_ID_XX"
1273            }
1274        ],
1275        […]
1276    },
1277
1278    curl -i -H 'Authorization: token XX_TOKEN_XX'
1279        'XX_RT_URL_XX/REST/2.0/download/cf/XX_IMAGE_OR_BINARY_OCFV_ID_XX'
1280        > file.png
1281
1282=head2 Paging
1283
1284All plural resources (such as C</tickets>) require pagination, controlled by
1285the query parameters C<page> and C<per_page>.  The default page size is 20
1286items, but it may be increased up to 100 (or decreased if desired).  Page
1287numbers start at 1. The number of pages is returned, and if there is a next
1288or previous page, then the URL for that page is returned in the next_page
1289and prev_page variables respectively. It is up to you to store the required
1290JSON to pass with the following page request.
1291
1292=head2 Disabled items
1293
1294By default, only enabled objects are returned. To include disabled objects
1295you can specify C<find_disabled_rows=1> as a query parameter.
1296
1297=head2 Fields
1298
1299When fetching search results you can include additional fields by adding
1300a query parameter C<fields> which is a comma seperated list of fields
1301to include. You must use the camel case version of the name as included
1302in the results for the actual item.
1303
1304You can use additional fields parameters to expand child blocks, for
1305example (line wrapping inserted for readability):
1306
1307    XX_RT_URL_XX/REST/2.0/tickets
1308      ?fields=Owner,Status,Created,Subject,Queue,CustomFields,Requestor,Cc,AdminCc,RT::CustomRole-1
1309      &fields[Queue]=Name,Description
1310
1311Says that in the result set for tickets, the extra fields for Owner, Status,
1312Created, Subject, Queue, CustomFields, Requestor, Cc, AdminCc and
1313CustomRoles should be included. But in addition, for the Queue block, also
1314include Name and Description. The results would be similar to this (only one
1315ticket is displayed in this example):
1316
1317   "items" : [
1318      {
1319         "Subject" : "Sample Ticket",
1320         "id" : "2",
1321         "type" : "ticket",
1322         "Owner" : {
1323            "id" : "root",
1324            "_url" : "XX_RT_URL_XX/REST/2.0/user/root",
1325            "type" : "user"
1326         },
1327         "_url" : "XX_RT_URL_XX/REST/2.0/ticket/2",
1328         "Status" : "resolved",
1329         "Created" : "2018-06-29:10:25Z",
1330         "Queue" : {
1331            "id" : "1",
1332            "type" : "queue",
1333            "Name" : "General",
1334            "Description" : "The default queue",
1335            "_url" : "XX_RT_URL_XX/REST/2.0/queue/1"
1336         },
1337         "CustomFields" : [
1338             {
1339                 "id" : "1",
1340                 "type" : "customfield",
1341                 "_url" : "XX_RT_URL_XX/REST/2.0/customfield/1",
1342                 "name" : "My Custom Field",
1343                 "values" : [
1344                     "CustomField value"
1345                 ]
1346             }
1347         ],
1348         "Requestor" : [
1349            {
1350               "id" : "root",
1351               "type" : "user",
1352               "_url" : "XX_RT_URL_XX/REST/2.0/user/root"
1353            }
1354         ],
1355         "Cc" : [
1356            {
1357               "id" : "root",
1358               "type" : "user",
1359               "_url" : "XX_RT_URL_XX/REST/2.0/user/root"
1360            }
1361         ],
1362         "AdminCc" : [],
1363         "RT::CustomRole-1" : [
1364            {
1365               "_url" : "XX_RT_URL_XX/REST/2.0/user/foo@example.com",
1366               "type" : "user",
1367               "id" : "foo@example.com"
1368            }
1369         ]
1370      }
1371      { … },
13721373   ],
1374
1375If the user performing the query doesn't have rights to view the record
1376(or sub record), then the empty string will be returned.
1377
1378For single object URLs like /ticket/:id, as it already contains all the
1379fields by default, parameter "fields" is not needed, but you can still
1380use additional fields parameters to expand child blocks:
1381
1382    XX_RT_URL_XX/REST/2.0/ticket/1?fields[Queue]=Name,Description
1383
1384=head2 Authentication Methods
1385
1386Authentication should B<always> be done over HTTPS/SSL for
1387security. You should only serve up the C</REST/2.0/> endpoint over SSL.
1388
1389=head3 Basic Auth
1390
1391Authentication may use internal RT usernames and passwords, provided via
1392HTTP Basic auth. Most HTTP libraries already have a way of providing basic
1393auth credentials when making requests.  Using curl, for example:
1394
1395    curl -u 'username:password' /path/to/REST/2.0
1396
1397=head3 Token Auth
1398
1399You may use the L<RT::Authen::Token> extension to authenticate to the
1400REST 2 API. Once you've acquired an authentication token in the web
1401interface, specify the C<Authorization> header with a value of "token"
1402like so:
1403
1404    curl -H 'Authorization: token …' /path/to/REST/2.0
1405
1406If the library or application you're using does not support specifying
1407additional HTTP headers, you may also pass the authentication token as a
1408query parameter like so:
1409
1410    curl /path/to/REST/2.0?token=…
1411
1412=head3 Cookie Auth
1413
1414Finally, you may reuse an existing cookie from an ordinary web session
1415to authenticate against REST2. This is primarily intended for
1416interacting with REST2 via JavaScript in the browser. Other REST
1417consumers are advised to use the alternatives above.
1418
1419=head2 Conditional requests (If-Modified-Since, If-Match)
1420
1421You can take advantage of the C<Last-Modified> headers returned by most
1422single resource endpoints.  Add a C<If-Modified-Since> header to your
1423requests for the same resource, using the most recent C<Last-Modified>
1424value seen, and the API may respond with a 304 Not Modified.  You can
1425also use HEAD requests to check for updates without receiving the actual
1426content when there is a newer version. You may also add an
1427C<If-Unmodified-Since> header to your updates to tell the server to
1428refuse updates if the record had been changed since you last retrieved
1429it.
1430
1431C<ETag>, C<If-Match>, and C<If-None-Match> work similarly to
1432C<Last-Modified>, C<If-Modified-Since>, and C<If-Unmodified-Since>,
1433except that they don't use a timestamp, which has its own set of
1434tradeoffs. C<ETag> is an opaque value, so it has no meaning to consumers
1435(unlike timestamps). However, timestamps have the disadvantage of having
1436a resolution of seconds, so two updates happening in the same second
1437would produce incorrect results, whereas C<ETag> does not suffer from
1438that problem.
1439
1440=head2 Status codes
1441
1442The REST API uses the full range of HTTP status codes, and your client should
1443handle them appropriately.
1444
1445=cut
1446
1447# XXX TODO: API doc
1448
1449sub to_psgi_app {
1450    my $self = shift;
1451    my $res = $self->to_app(@_);
1452
1453    return Plack::Util::response_cb($res, sub {
1454        my $res = shift;
1455        $self->CleanupRequest;
1456    });
1457}
1458
1459sub to_app {
1460    my $class = shift;
1461
1462    return builder {
1463        enable '+RT::REST2::Middleware::ErrorAsJSON';
1464        enable '+RT::REST2::Middleware::Log';
1465        enable '+RT::REST2::Middleware::Auth';
1466        RT::REST2::Dispatcher->to_psgi_app;
1467    };
1468}
1469
1470sub base_path {
1471    RT->Config->Get('WebPath') . $REST_PATH
1472}
1473
1474sub base_uri {
1475    RT->Config->Get('WebBaseURL') . shift->base_path
1476}
1477
1478# Called by RT::Interface::Web::Handler->PSGIApp
1479sub PSGIWrap {
1480    my ($class, $app) = @_;
1481    return builder {
1482        mount $REST_PATH => $class->to_app;
1483        mount '/' => $app;
1484    };
1485}
1486
1487sub CleanupRequest {
1488
1489    if ( $RT::Handle && $RT::Handle->TransactionDepth ) {
1490        $RT::Handle->ForceRollback;
1491        $RT::Logger->crit(
1492            "Transaction not committed. Usually indicates a software fault."
1493            . "Data loss may have occurred" );
1494    }
1495
1496    # Clean out the ACL cache. the performance impact should be marginal.
1497    # Consistency is imprived, too.
1498    RT::Principal->InvalidateACLCache();
1499    DBIx::SearchBuilder::Record::Cachable->FlushCache
1500      if ( RT->Config->Get('WebFlushDbCacheEveryRequest')
1501        and UNIVERSAL::can(
1502            'DBIx::SearchBuilder::Record::Cachable' => 'FlushCache' ) );
1503}
1504
15051;
1506