Log time on an Autotask or Halo ticket, which SuperIT can't do natively, with an action that calls your PSA's REST API

Log a time entry on an Autotask ticket by calling Autotask's REST API from a workflow action, so engineers can say "log 20 minutes on this ticket" in chat. For MSPs on Autotask or Halo, which SuperIT can't log time to natively.

Runs in Cloud sandbox (PowerShell)
Uses API key credential (Autotask PSA)
Suggested approval No approval needed for engineers (policy rule: Specific groups → No approval needed)
Category Integrations

Test it with SuperIT before you rely on it

This is an example, not a ready-made action, and it hasn't been tested end to end. The Autotask address, TimeEntries field names and ApiIntegrationCode header come from Autotask's public API documentation; check them against the current Autotask REST API. Before you use it, work through it with SuperIT: ask SuperIT to review the script against your environment and requirements, and adjust it together. Then test it, as in Test it. Test in the action editor isn't a dry run: it runs the script for real.

Why use an action instead of asking SuperIT

If you use ConnectWise, you don't need this action: just ask SuperIT to "log 20 minutes on this ticket". It shows an approval card and confirms the entry was recorded. SuperIT can't log time to Autotask or Halo today, and can't call your PSA's API with a credential of its own, so this action holds an Autotask API user's keys as an encrypted credential and posts the time entry for you.

SuperIT already writes these to your PSA without any action, so just ask in chat:

  • Time entries on ConnectWise tickets, from engineer and admin chats, after an approval card.
  • Internal or customer-visible notes on tickets in any connected PSA.
  • Triage and investigation results, posted as internal notes when ticket automation has a note location set to PSA internal note, and triage field updates with Update PSA ticket.
  • Notes on approval, completion and error for action runs linked to a PSA ticket.

When to use it

  • "Log 20 minutes on ticket 48211 for the printer fix." (Autotask)
  • "Add my time for this call to the ticket: 15 minutes, reset the user's VPN profile."
  • "Summarise what we did and log 30 minutes with that summary."

What the action does

  1. Validates the ticket ID, minutes (1–480), resource ID and notes, and checks the resource ID is on the script's list of your engineers.
  2. Works out a start and end time that ends now and lasts the given number of minutes, and the date worked in your time zone.
  3. Creates a time entry against the Autotask ticket for the engineer's Autotask resource, with the notes as the summary, using the API keys from the linked credential.
  4. Prints the new time entry ID, or Autotask's error message.

What it doesn't do:

  • It doesn't change ticket status, assign the ticket or close it.
  • It doesn't check that the time is billable or that the resource may charge to that ticket's contract. Autotask applies its own rules and returns an error if they fail.
  • It doesn't know who asked. Scripts don't receive the requester's identity, so anyone who can run the action can log time as any resource on the list. Keep the list to your own engineers, and review time entries as you do today.
  • It doesn't use SuperIT's own PSA connection. It uses a separate API user that you create for this action.

Before you begin

  • An Autotask API user with permission to add time entries. Use a dedicated API user, not the one SuperIT's PSA connection uses, so you can limit and audit it separately.
  • Your Autotask zone's REST URL, such as https://webservices5.autotask.net/atservicesrest/v1.0. Autotask's zone information endpoint returns it for your API user.
  • An API tracking identifier (ApiIntegrationCode) from Autotask. It identifies the integration, isn't a secret, and goes in the script.
  • The role ID your engineers bill ticket time as, from Admin → Resources/Users (HR) → Roles in Autotask.
  • Your engineers' Autotask resource IDs, to list in the resource_id select input and in the script.
  • Your time zone, as an IANA name such as Australia/Sydney, so the date worked is your local date.
  • A credential. Under Workflows → Credentials, select New, name it Autotask and choose the Autotask PSA Auth scheme. Enter the API user's Username and Secret. The script reads them as CREDENTIAL_AUTOTASK_USERNAME and CREDENTIAL_AUTOTASK_SECRET. See Credentials.
  • An authorization policy that lets engineers run it without approval, for example one rule: Requester Specific groups superit:engineers, Approver No approval needed. Create the group under Users → Manage groups. See Approvals.
  • Your role. Creating an action in chat needs the Admin role. Engineers can build it by hand under Workflows → Actions.

Build it with SuperIT

Create an action called "Log time on Autotask ticket".
Purpose: add a time entry to an Autotask ticket, ending now.
Runtime: PowerShell in the cloud sandbox.
Inputs: ticket_id (string, required - Autotask ticket ID), minutes (string, required, 1-480), notes (textarea, required), resource_id (select, required - our engineers' Autotask resource IDs: <IDs>; check it against the same list in the script).
Credential: "Autotask" (Autotask PSA preset: UserName and Secret headers). Our REST URL is https://webservices5.autotask.net/atservicesrest/v1.0, our ApiIntegrationCode is <your code>, our role ID is <role ID> and our time zone is <IANA time zone>; dateWorked is today's date in that time zone.
It writes: one POST to /TimeEntries with ticketID, resourceID, roleID, dateWorked, startDateTime, endDateTime, hoursWorked and summaryNotes. Nothing else.
Authorization policy: superit:engineers group, no approval.

SuperIT confirms what the action changes, drafts the script, validates it and creates the action as a draft. You review it, test it on a test ticket and publish it under Workflows → Actions.

Or build it yourself

Name Type Required Example
ticket_id string Yes 48211
minutes string Yes 20
notes textarea Yes Cleared the print queue and restarted the spooler.
resource_id select Yes 29682885

Enter your engineers' resource IDs under Choices (comma-separated), and the same IDs in $allowedResources. Set Runtime to PowerShell:

# Goal: add one time entry, ending now, to an Autotask ticket. It changes nothing else.
$baseUrl  = "https://webservices5.autotask.net/atservicesrest/v1.0"   # your zone's REST URL
$integrationCode = "YOUR-API-TRACKING-IDENTIFIER"                     # ApiIntegrationCode, not a secret
$roleId   = 29683000                                                  # the role engineers bill ticket time as
$allowedResources = @('29682885', '29682886')                        # your engineers' resource IDs
$timeZone = "Australia/Sydney"                                        # IANA name, for the date worked
$userName = $env:CREDENTIAL_AUTOTASK_USERNAME
$secret   = $env:CREDENTIAL_AUTOTASK_SECRET

function Invoke-TicketTimeEntry {
    param([string]$TicketId, [string]$Minutes, [string]$Notes, [string]$ResourceId)

    $ticket = "$TicketId".Trim()
    $resource = "$ResourceId".Trim()
    $text = "$Notes".Trim()
    Assert-Inputs -TicketId $ticket -ResourceId $resource -Notes $text
    $duration = ConvertTo-Minutes -Minutes $Minutes
    $body = New-TimeEntryBody -TicketId $ticket -Minutes $duration -ResourceId $resource -Notes $text
    $entryId = Add-TimeEntry -TicketId $ticket -Body $body
    Write-TimeEntrySummary -TicketId $ticket -Minutes $duration -ResourceId $resource -EntryId $entryId
    # $script: so the runner can still read $result after this function returns.
    $script:result = @{ ticket_id = $ticket; minutes = $duration; resource_id = $resource; time_entry_id = $entryId }
}

function Assert-Inputs {
    param([string]$TicketId, [string]$ResourceId, [string]$Notes)
    if ($TicketId -notmatch '^\d+$') { throw "ticket_id must be the numeric Autotask ticket ID." }
    if ($ResourceId -notmatch '^\d+$') { throw "resource_id must be a numeric Autotask resource ID." }
    if ($allowedResources -notcontains $ResourceId) { throw "resource_id $ResourceId isn't on this action's list of engineers." }
    if (-not $Notes) { throw "notes is required." }
    if (-not $userName -or -not $secret) { throw "The 'Autotask' credential is not linked to this action." }
}

function ConvertTo-Minutes {
    param([string]$Minutes)
    $value = 0
    if (-not [int]::TryParse($Minutes, [ref]$value) -or $value -lt 1 -or $value -gt 480) {
        throw "minutes must be a whole number from 1 to 480."
    }
    return $value
}

function New-TimeEntryBody {
    param([string]$TicketId, [int]$Minutes, [string]$ResourceId, [string]$Notes)
    $end   = [DateTime]::UtcNow
    $start = $end.AddMinutes(-$Minutes)
    # The UTC date is still yesterday during an Australian morning, so use the local date.
    $localDay = [TimeZoneInfo]::ConvertTimeBySystemTimeZoneId($end, $timeZone).ToString("yyyy-MM-dd")
    return @{
        ticketID      = [int64]$TicketId
        resourceID    = [int64]$ResourceId
        roleID        = $roleId
        dateWorked    = "$($localDay)T00:00:00Z"
        startDateTime = $start.ToString("yyyy-MM-ddTHH:mm:ssZ")
        endDateTime   = $end.ToString("yyyy-MM-ddTHH:mm:ssZ")
        hoursWorked   = [math]::Round($Minutes / 60, 2)
        summaryNotes  = $Notes
    } | ConvertTo-Json -Depth 5
}

function Add-TimeEntry {
    param([string]$TicketId, [string]$Body)
    $headers = @{ UserName = $userName; Secret = $secret; ApiIntegrationCode = $integrationCode }
    try {
        $response = Invoke-RestMethod -Method Post -Uri "$baseUrl/TimeEntries" -Headers $headers `
            -ContentType "application/json" -Body $Body -TimeoutSec 30
    } catch {
        # Autotask puts the reason (completed ticket, role not allowed) in the response body.
        $detail = $_.ErrorDetails.Message
        throw "Failed to add a time entry to ticket '$TicketId': $($_.Exception.Message) $detail"
    }
    return $response.itemId
}

function Write-TimeEntrySummary {
    param([string]$TicketId, [int]$Minutes, [string]$ResourceId, $EntryId)
    Write-Output "=== TIME ENTRY ADDED ==="
    Write-Output "Ticket     : $TicketId"
    Write-Output "Minutes    : $Minutes"
    Write-Output "Resource   : $ResourceId"
    Write-Output "Time entry : $EntryId"
}

Invoke-TicketTimeEntry -TicketId $ctx.inputs.ticket_id -Minutes $ctx.inputs.minutes `
    -Notes $ctx.inputs.notes -ResourceId $ctx.inputs.resource_id

The ticket ID is Autotask's internal number, not the ticket number shown in the title (such as T20261101.0001). Before the run, check the ticket ID SuperIT filled in is the internal number of the right ticket, and give it the ID if it doesn't have it.

Test it

  1. Create a test ticket in Autotask, or use an internal ticket you can tidy up.
  2. Save the draft and select Test.
  3. Test is not a dry run: it skips authorization and approvals, uses the real credential and network access, and creates a real time entry. Test runs aren't billed.
  4. Run it against the test ticket with a few minutes and a short note. Check the time entry in Autotask, then delete it there.
  5. Try a ticket ID that doesn't exist and check that Autotask's error appears in Stderr.
  6. Select Publish.

Use it

  • In chat: while working a ticket with SuperIT, say "Log 20 minutes on this ticket: cleared the print queue. My Autotask resource ID is 29682885." SuperIT fills in the inputs from the chat; check the ticket ID and resource ID before the run.
  • At the end of a session: "Summarise what we did and log 30 minutes with that summary."
  • Managed teams: time entries usually go to the service provider's own PSA, so keep this action in the service provider team.

Variations

  • Halo: Halo's API records time on a ticket action. Check Halo's API documentation for the endpoint and the time field. Halo's API uses OAuth client credentials, so store the client ID and secret with Custom HTTP headers and fetch the token in the script, as in Look up device warranty.
  • Other PSA writes SuperIT doesn't make: use the same pattern, one write per action, each with its own approval.