Help Desk Software » Helpdesk API

Helpdesk API

Jitbit web-based help desk ticketing system includes a “RESTful” web-service that can be called from external apps (inbound integration) and API hooks that can call external apps (outbound integration).

Contents

All webservice calls return data in JSON format.

Base address

The API address is:

https://[helpdesk-url]/api

Where “[helpdesk-url]” is the helpdesk application address. Hosted or self-installed, doesn’t matter.

Don’t forget to add “/helpdesk” to the URL if your Helpdesk is hosted under a sub-directory. All hosted clients must add “/helpdesk” to the base URL.

Example of the correct URL:

https://company.jibit.com/helpdesk/api

It’s highly recommended to use “https://“ prefix, but “http://“ is also allowed.

Basic authentication

Every API action checks the user's permissions to perform the action (see tickets, post comments) etc. You must authenticate as a helpdesk user by sending basic authorization headers with every call, more on that later.

All the methods use basic authentication. You need to pass the “Authorization” header with every API call.

Most of http-client software (like the "curl" tool etc.") supports basic authentication without any additional coding. But just in case: here's how you construct the Authorization header:

  1. Username and password are combined into a string “username:password”
  2. The resulting string literal is then encoded using Base64
  3. The authorization method and a space i.e. “Basic ” is then put before the encoded string.
  4. NOTE: even if you use Windows authentication with your local helpdesk install you still have to use Basic auth headers! And pas your Windows usernames and passwords. And don't forget to include the user's domain into the username like this "DOMAIN\Username".

For example, if the user has ‘admin’ username and ‘admin’ password then the header is formed as follows:

NOTE: If you are using Windows\AD authentication specify the fully qualified domain name as your username, like DOMAIN\username.

Authorization: Basic YWRtaW46YWRtaW4=

Keep in mind that passing wrong username and/or password to the app 3 times in a row blocks your IP address for 5 minutes.

Passing parameters

All incoming method-parameters should be passed via query-strings (like this http://helpdesk-url/api/SetCustomField?ticketId=123&fieldId=321&value=blahblah) or via the POST data. Do not try to use JSON.

API methods:

Authorization

POST https://[helpdesk-url]/api/Authorization

Use this method to simply test if you pass correct authorization headers.

Returns:
{
 “UserID”: 1,
 “Username”: “admin”,
 “Password”: “21232f297a57a5a743894a0e4a801fc3”,
 “Email”: “[email protected]”,
 “FirstName”: "“,
 ”LastName“: ”“,
 ”Notes“: ”“,
 ”Location“: ”“,
 ”Phone“: ”“,
 ”Department“: null,
 ”CompanyName“: null,
 ”IPAddress“: ”::1“,
 ”HostName“: ”::1“,
 ”Lang“: ”“,
 ”UserAgent“: ”Mozilla/5.0 (Windows NT 6.2; WOW64) AppleWebKit/537.31 (KHTML, like Gecko) Chrome/26.0.1410.3 Safari/537.31“,
 ”AvatarURL“: null,
 ”Signature“: ”test sign“,
 ”Greeting“: ”Hi“,
 ”CompanyId“: null,
 ”CRMID“: null,
 ”TicketsHandled“: null,
 ”TicketsSubmitted“: null,
 ”IsAdmin“: true,
 ”Disabled“: false,
 ”SendEmail“: true,
 ”IsTech“: false,
 ”TicketsAssigned“: 0,
 ”LastSeen“: ”/Date(1360748934770)/“,
 ”RecentTickets“: null,
 ”IsManager“: false,
 ”FullNameAndLogin“: ”admin"
}

Tickets

Tickets

GET https://[helpdesk-url]/api/Tickets

Gets a list of tickets the user has permissions to see.

Parameters:

NameTypeDescription
modestring(otional) Allows you to choose, what tickets to show:
"all"(default) – all tickets, including closed
"unanswered" – shows new or updated by customer or for tech tickets
"unclosed" – all active tickets
"handledbyme" – shows tickets assigned to the user
categoryidint(otional) Filter by a category
sectionIdint(otional) Filter by a section
statusIdint(otional) Filter by a status
fromUseridint(otional) Filter by a ticket creator
fromCompanyIdint(otional) Filter by a company
handledByUserIDint(otional) Filter by a ticket performer
tagNamestring(otional) Filter by ticket a tag
dateFromstring(otional) Filter by creation date (date format should be YYYY-MM-DD, for example 2016-11-24)
dateTostring(otional) Filter by creation date (date format should be YYYY-MM-DD, for example 2016-11-24)
countint(otional) How many tickets to return. Default: 10. Max: 100.
offsetint(otional) Use this to create paging. For example “offset=20&count=20” will return the next 20 tickets after the first 20. Defalut: 0.

Returns:


 [
 {
 “IssueID”: 382,
 “Priority”: 0,
 “StatusID”: 1,
 “IssueDate”: “2013–01–11T15:29:49.57”,
 “Subject”: “test”,
 “Status”: “New”,
 “UpdatedByUser”: false,
 “UpdatedByPerformer”: false,
 “CategoryID”: 21,
 “UserName”: “admin”,
 “Technician”: null,
 “FirstName”: "“,
 ”LastName“: ”“,
 ”DueDate“: null,
 ”TechFirstName“: null,
 ”TechLastName“: null,
 ”LastUpdated“: ”2013–01–11T15:29:49.57“,
 ”UpdatedForTechView“: false,
 ”UserID“: 1,
 ”CompanyID“: null,
 ”CompanyName“: null,
 ”SectionID“: null,
 ”AssignedToUserID“: null,
 ”Category“: ”Hardware“
 },
 {
 ”IssueID“: 466,
 ”Priority“: 0,
 ”StatusID“: 1,
 ”IssueDate“: ”2013–02–05T00:27:14.133“,
 ”Subject“: ”Test ticket“,
 ”Status“: ”New“,
 ”UpdatedByUser“: false,
 ”UpdatedByPerformer“: true,
 ”CategoryID“: 1,
 ”UserName“: ”[email protected]“,
 ”Technician“: ”admin“,
 ”FirstName“: ”“,
 ”LastName“: ”“,
 ”DueDate“: null,
 ”TechFirstName“: ”“,
 ”TechLastName“: ”“,
 ”LastUpdated“: ”2013–02–08T20:51:22.437“,
 ”UpdatedForTechView“: false,
 ”UserID“: 13,
 ”CompanyID“: null,
 ”CompanyName“: null,
 ”SectionID“: 1,
 ”AssignedToUserID“: 1,
 ”Category“: ”Sales - General sales“,
 ”cf1“: null,
 ”cf9": null
 }
 ]

Ticket

GET https://[helpdesk-url]/api/ticket

Gets details of a ticket

Parameters:

NameTypeDescription
idintTicket id

Returns:

{
    "Attachments": [
        {
            "FileName": "icon.png",
            "FileData": null,
            "FileID": 1740828,
            "CommentID": 12722431,
            "CommentDate": "/Date(1428513969800)/",
            "FileHash": null,
            "FileSize": 0,
            "IssueID": 2431395,
            "UserID": 43499,
            "GoogleDriveUrl": null,
            "DropboxUrl": null,
            "ForTechsOnly": false,
            "Url": "https://support.jitbit.com/helpdesk//helpdesk/GetAttachment.ashx?FileID=1740828"
        }
    ],
    "Tags": [
        {
            "TagID": 14502,
            "Name": "tag1",
            "TagCount": 0
        },
        {
            "TagID": 14503,
            "Name": "tag2",
            "TagCount": 0
        }
    ],
    "Status": "In progress",
    "OnBehalfUserName": null,
    "SubmitterUserInfo": {
        "UserID": 43499,
        "InstanceID": 621,
        "Username": "Max",
        "Password": null,
        "Email": "[email protected]",
        "FirstName": "Max",
        "LastName": "",
        "Notes": "test",
        "Location": "",
        "Phone": "+16463977708",
        "Department": null,
        "CompanyName": "Jitbit Software",
        "IPAddress": "213.229.75.25",
        "HostName": "213.229.75.25",
        "Lang": "en-US",
        "UserAgent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_10_3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/41.0.2272.118 Safari/537.36",
        "AvatarURL": null,
        "Signature": "Cheers,\r\nMax",
        "Greeting": "Hi #FirstName#",
        "CompanyId": 451,
        "CRMID": 2200,
        "CompanyNotes": null,
        "TicketsHandled": null,
        "TicketsSubmitted": null,
        "IsAdmin": true,
        "Disabled": false,
        "SendEmail": false,
        "IsTech": false,
        "TicketsAssigned": 0,
        "LastSeen": "/Date(1428513697957)/",
        "RecentTickets": null,
        "IsManager": false,
        "TechTutorialShown": true,
        "AdminTutorialShown": true,
        "PushToken": null,
        "FullNameAndLogin": "Max",
        "FullName": "Max"
    },
    "CategoryName": "General issues",
    "AssigneeUserInfo": {
        "UserID": 43499,
        "InstanceID": 621,
        "Username": "Max",
        "Password": null,
        "Email": "[email protected]",
        "FirstName": "Max",
        "LastName": "",
        "Notes": "test",
        "Location": "",
        "Phone": "+16463977708",
        "Department": null,
        "CompanyName": "Jitbit Software",
        "IPAddress": "213.229.75.25",
        "HostName": "213.229.75.25",
        "Lang": "en-US",
        "UserAgent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_10_3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/41.0.2272.118 Safari/537.36",
        "AvatarURL": null,
        "Signature": "Cheers,\r\nMax",
        "Greeting": "Hi #FirstName#",
        "CompanyId": 451,
        "CRMID": 2200,
        "CompanyNotes": null,
        "IsAdmin": true,
        "Disabled": false,
        "SendEmail": false,
        "IsTech": false,
        "LastSeen": "/Date(1428513697957)/",
        "IsManager": false,
        "PushToken": null,
        "FullNameAndLogin": "Max",
        "FullName": "Max"
    },
    "TicketID": 2431395,
    "UserID": 43499,
    "AssignedToUserID": 43499,
    "IssueDate": "/Date(1428513709633)/",
    "Subject": "test",
    "Body": "test ticket",
    "Priority": 0,
    "StatusID": 2,
    "CategoryID": 7277,
    "DueDate": null,
    "ResolvedDate": null,
    "StartDate": "/Date(1428513969817)/",
    "TimeSpentInSeconds": 143,
    "UpdatedByUser": false,
    "UpdatedByPerformer": true,
    "UpdatedForTechView": false,
    "IsCurrentUserTechInThisCategory": false,
    "IsCurrentCategoryForTechsOnly": false,
    "SubmittedByCurrentUser": true,
    "IsInKb": false,
    "Stats": null
}

POST Ticket

POST https://[helpdesk-url]/api/ticket

Creates a new ticket

Parameters:

NameTypeDescription
categoryIdintCategory ID
bodystringTicket body
subjectstringTicket subject
priorityIdintTicket priority. Values:
-1 – Low
0 – Normal
1 – High
2 – Critical
userId (optional)intUser-ID to create a ticket "on-behalf" of this user (requires technician permissions)
tags (optional)stringA string of tags separated by comma. Example: tags=tag1,tag2,tag3

This method supports file attachments. To attach a file you need to send a POST request with "Content-Type: multipart/form-data;" with each parameter, including the file, as a separate "part". Search how to do "form data post requests" in your programming language. Example curl call:

curl -F "categoryId=1" -F "body=test" -F "subject=test" -F "priorityId=0" -F "[email protected]" -u admin:admin -v http://localhost/helpdesk/api/ticket

Returns:
The created ticket ID

UpdateTicket

POST https://[helpdesk-url]/api/UpdateTicket

Change ticket parameters, one or many.

Parameters:

idintTicket ID
categoryId (optional)intTicket category
priority (optional)intTicket priority. Values:
-1 – Low
0 – Normal
1 – High
2 – Critical
date (optional)DateTimeTicket creation date
dueDate (optional)DateTimeDue date
assignedUserId (optional)intAssigned technician's ID. Set to 0 (zero) to remove the currently assigned user.
timeSpentInSeconds (optional)intTime spent on the ticket
statusId (optional)intTicket status ID. "Closed" id 3, "New" is 1, "In process" is 2. Check your custom status IDs in the admin area

Returns:
200 OK if there were no errors. Returns an error message otherwise.

Example:
POST https://[helpdesk-url]/api/UpdateTicket?id=321&dueDate=2018-12-01

POST SetCustomField

POST https://[helpdesk-url]/api/SetCustomField

Sets custom field value in a ticket.

Parameters:

NameTypeDescription
ticketIdintTicket ID
fieldIdintCustom field ID
valuestringValue as a string. For checkboxes pass true or false. For dropdowns pass the option ID. For dates pass date as a string in any format.

Returns:
200 OK if there were no errors. Returns an error message otherwise.

GET TicketCustomFields

GET https://[helpdesk-url]/api/TicketCustomFields?id=123

View all ticket custom fields with their values

Parameters:

NameTypeDescription
idintTicket ID

Returns:

[
    {
        "FieldName": "Helpdesk URL",
        "FieldID": 2903,
        "Type": 1,
        "UsageType": 0,
        "Value": null,
        "ValueWithOption": null,
        "Mandatory": false
    },
    {
        "FieldName": "Self-hosted version",
        "FieldID": 2904,
        "Type": 4,
        "UsageType": 0,
        "Value": null,
        "ValueWithOption": null,
        "Mandatory": false
    }
]

Custom field types

Text = 1,
Date = 2,
SelectionCombo = 3,
Checkbox = 4,
MultilineText = 5

Attachment

GET https://[helpdesk-url]/api/attachment

Allows you to download an individual file attachment

Parameters:

NameTypeDescription
idintFile attachment ID

Returns:
The requested file attachment. Look at the "Content-Type" header for the file type. You can find the file name in the "Content-disposition" header.

Categories

Categories

GET https://[helpdesk-url]/api/categories

Gets all categories the user has permissions to see.

Parameters:
None

Returns:


 [
    {
        "CategoryID": 7277,
        "Name": "General issues",
        "SectionID": null,
        "Section": null,
        "NameWithSection": "General issues",
        "ForTechsOnly": false,
        "FromAddress": null
    },
    {
        "CategoryID": 7273,
        "Name": "Helpdesk",
        "SectionID": 2587,
        "Section": "Product specific",
        "NameWithSection": "Product specific \\ Helpdesk",
        "ForTechsOnly": false,
        "FromAddress": null
    }
]

GET TechsForCategory

GET https://[helpdesk-url]/api/TechsForCategory?id=123

Gets all possible assignees for a category

Parameters:

NameTypeDescription
idintCategory ID

Returns:

[
    {
        "UserID": 43499,
        "Username": "Max",
        "FirstName": "Max",
        "LastName": "",
        "TicketsAssigned": 16,
        "FullNameAndLogin": "Max",
        "FullName": "Max"
    },
    {
        "UserID": 239146,
        "Username": "Vlad",
        "FirstName": "Vlad",
        "LastName": "",
        "TicketsAssigned": 51,
        "FullNameAndLogin": "Vlad",
        "FullName": "Vlad"
    }
]

Comments

Comment

POST https://[helpdesk-url]/api/comment

Posts a comment to a ticket

Parameters:

NameTypeDescription
idintTicket id
bodystringComment body
forTechsOnlyboolIf this comment is for techs only. Default: false

This method supports file attachments. To attach a file you need to send a POST request with "Content-Type: multipart/form-data;" with each parameter, including the file, as a separate "part". Search how to do "form data post requests" in your programming language. Example curl call:

curl -F "id=1" -F "body=test" -F "[email protected]" -u admin:admin -v http://localhost/helpdesk/api/comment

Comments

GET https://[helpdesk-url]/api/comments

Gets comments of a specified ticket

Parameters:

NameTypeDescription
idintTicket id

Returns:


 [
 {
 “CommentID”: 1828,
 “IssueID”: 471,
 “UserID”: 1,
 “UserName”: “admin”,
 “Email”: “[email protected]”,
 “FirstName”: "“,
 ”LastName“: ”“,
 ”IsSystem“: false,
 ”CommentDate“: ”/Date(1360750914287)/“,
 ”ForTechsOnly“: false,
 ”Body“: ”Hi\r\n\r\n“,
 ”TicketSubject“: null,
 ”Recipients“: ”“,
 ”Attachments“: []
 },
 {
 ”CommentID“: 1827,
 ”IssueID“: 471,
 ”UserID“: 1,
 ”UserName“: ”admin“,
 ”Email“: ”[email protected]“,
 ”FirstName“: ”“,
 ”LastName“: ”“,
 ”IsSystem“: true,
 ”CommentDate“: ”/Date(1360750494897)/“,
 ”ForTechsOnly“: false,
 ”Body“: ”The ticket has been taken“,
 ”TicketSubject“: null,
 ”Recipients“: ”“,
 ”Attachments": []
 }]

CommentTemplates (Canned responses)

GET https://[helpdesk-url]/api/CommentTemplates

Gets all available canned responses from your Helpdesk

Returns:

[{
    "TemplateId": 1420,
    "Body": "You need to set Helpdesk's application pool to run under .NET 4.0 instead of 2.0 in IIS.",
    "Name": "4.0 instead of 2.0"
},
{
    "TemplateId": 362,
    "Body": "Could you please tell me, what app are you talking about exactly?",
    "Name": "Which app??"
}]

Users

CreateUser

POST https://[helpdesk-url]/api/CreateUser

Creates a new user

Parameters:

NameTypeDescription
usernamestringusername, should not be taken by another user
passwordstringuser's password
emailstringuser's email
firstNamestringfirst name
lastNamestringsurname
phonestringphone
locationstringlocation
companystringSet user's company. If the company doesn't exist, it will be created.
departmentstringSet user's department. If department doesn't exist, it will be created.
sendWelcomeEmailboolSend a "welcome" to the user

Returns: userId

UpdateUser

POST https://[helpdesk-url]/api/UpdateUser

Updates a user

Parameters:

NameTypeDescription
userIdintedited user's ID
usernamestringusername, should not be taken by another user
emailstringuser's email
firstNamestringfirst name
lastNamestringsurname
notesstringoptional administrator's notes
phonestringphone
locationstringlocation
departmentstringuser's department name
disabledboolenable/disable the user
companybooluser's company name

GET UserByEmail

GET https://[helpdesk-url]/api/[email protected]

Gets all information about a user

Parameters:

NameTypeDescription
emailstringemail address

Returns:

{
    "UserID": 43499,
    "InstanceID": 621,
    "Username": "Max",
    "Password": null,
    "Email": "[email protected]",
    "FirstName": "Max",
    "LastName": "",
    "Notes": "test",
    "Location": "",
    "Phone": "+16463977708",
    "Department": null,
    "CompanyName": "Jitbit Software",
    "IPAddress": "213.229.75.25",
    "HostName": "213.229.75.25",
    "Lang": "en-US",
    "UserAgent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_10_3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/41.0.2272.118 Safari/537.36",
    "AvatarURL": null,
    "Signature": "Cheers,\r\nMax",
    "Greeting": "Hi #FirstName#",
    "CompanyId": 451,
    "CRMID": 2200,
    "CompanyNotes": null,
    "IsAdmin": true,
    "Disabled": false,
    "SendEmail": false,
    "IsTech": false,
    "LastSeen": "/Date(1428513697957)/",
    "IsManager": false,
    "PushToken": null,
    "FullNameAndLogin": "Max",
    "FullName": "Max"
}

GET Users

GET https://[helpdesk-url]/api/Users

Gets a list of all users

Parameters:

NameTypeDescription
countintnumber of users to return. Default: 500
pageintused to get the next set of users after the first one. So ?count=50 returns the first 50 users and ?count=50&page=2 returns the following 50 users.
listModestring"all" (default) - all users
"techs" - techs including admins
"admins" - admins only
"regular" - only regular users

Returns:

[
  {
    "InstanceID": 0,
    "FirstName": "",
    "LastName": "",
    "Notes": null,
    "Location": "",
    "Phone": "",
    "CompanyName": null,
    "IPAddress": null,
    "HostName": null,
    "Lang": null,
    "UserAgent": null,
    "AvatarURL": null,
    "Signature": null,
    "Greeting": null,
    "CompanyId": null,
    "DepartmentID": null,
    "CRMID": null,
    "CompanyNotes": null,
    "TicketsHandled": null,
    "TicketsSubmitted": null,
    "SendEmail": true,
    "IsTech": false,
    "TicketsAssigned": 0,
    "LastSeen": null,
    "RecentTickets": null,
    "IsManager": false,
    "PushToken": null,
    "PushPlatform": null,
    "TwoFactorAuthEnabled": false,
    "TimezoneID": null,
    "FullNameAndLogin": "[email protected]",
    "FullName": "[email protected]",
    "UserID": 89,
    "Username": "[email protected]",
    "Password": null,
    "Email": "[email protected]",
    "IsAdmin": false,
    "Disabled": false
  }
  ...
]

GET Companies

GET https://[helpdesk-url]/api/Companies

Gets a list of all companies

Returns:

[
  {
    "CompanyID": 1,
    "InstanceID": 0,
    "Name": "test company",
    "EmailDomain": null,
    "Notes": null
  }
  ...
]

Company

POST https://[helpdesk-url]/api/Company

Creates a new company

Parameters:

NameTypeDescription
namestringcompany name
emailDomainsstring(optional) one or more company email domains. Separate multiple values with semicolon like this "empire.com;deathstar.com;vader.com"

Returns: Created company id or an id of an existing company if the supplied name was taken

Knowledge base

Articles

GET https://[helpdesk-url]/api/Articles

Returns a list of your Knowledge base articles with titles, full URLs and other useful information, excluding the actual article content.

Parameters:

categoryIdoptionalReturn articles from a particular category

Returns:

{
    "Articles": [
        {
            "Attachments": [],
            "ArticleId": 6,
            "Subject": "Sample KB article",
            "Body": null,
            "ForTechsOnly": false,
            "CategoryID": 5,
            "CategoryName": null,
            "CategoryNotes": null,
            "TagString": null,
            "UrlId": "6-sample-kb-article",
            "DateCreated": "2016-10-09T05:04:40.89",
            "LastUpdated": null,
            "Url": "http://localhost:3000//KB/View/6-sample-kb-article",
            "Tags": null
        },
        {
            "Attachments": [],
            "ArticleId": 7,
            "Subject": "Another sample KB article",
            "Body": null,
            "ForTechsOnly": false,
            "CategoryID": 6,
            "CategoryName": null,
            "CategoryNotes": null,
            "TagString": null,
            "UrlId": "7-another-sample-kb-article",
            "DateCreated": "2016-10-09T05:04:40.893",
            "LastUpdated": null,
            "Url": "http://localhost:3000//KB/View/7-another-sample-kb-article",
            "Tags": null
        }
    ],
    "Categories": [
        {
            "CategoryID": 5,
            "Name": "Payment issues",
            "SectionID": 1,
            "Section": "Sales",
            "TicketCount": 1,
            "NameWithSection": "Sales \\ Payment issues",
            "ForTechsOnly": false,
            "ForSpecificUsers": false,
            "FromAddress": null,
            "KBOnly": false
        },
        {
            "CategoryID": 6,
            "Name": "Pre-sales questions",
            "SectionID": 1,
            "Section": "Sales",
            "TicketCount": 1,
            "NameWithSection": "Sales \\ Pre-sales questions",
            "ForTechsOnly": false,
            "ForSpecificUsers": false,
            "FromAddress": null,
            "KBOnly": false
        }
    ],
    "Tags": []
}

Article

GET https://[helpdesk-url]/api/Article/%id%

Returns a particular Knowledge Base article along with its content.

Parameters:

idrequiredArticle ID

Returns:

{
    "Attachments": [],
    "ArticleId": 7,
    "Subject": "Another sample KB article",
    "Body": "This is a sample Knowledge Base article, just to give you a clue what it looks like.\r\n\r\n[b]Subtitle[/b]\r\n\r\nHere you can explain the knowledge base article in detail. If applicable, explain how the problem can be worked around.",
    "ForTechsOnly": false,
    "CategoryID": 6,
    "CategoryName": "Pre-sales questions",
    "CategoryNotes": null,
    "TagString": null,
    "UrlId": null,
    "DateCreated": "2016-10-09T05:04:40.893",
    "LastUpdated": "2016-10-09T05:04:40.893",
    "Url": "http://localhost:3000//KB/View/7",
    "Tags": []
}

Authentication API

You can also utilize the Authentication API that is included in both the hosted and self-hosted version.

Last updated: 9/22/2017 more Helpdesk Ticketing System whitepapers Helpdesk API

Help Desk Software