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 { … }, 1109 … 1110 ] 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 { … }, 1372 … 1373 ], 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