Concorbit HelpAll guides →

Overview

Autotask PSA integration

concorbit's Autotask bridge syncs data with your Autotask PSA so engineers can work in concorbit and your existing Autotask records stay in lock-step. The bridge is two-directional: concorbit can pull data from AT (the default) and, once you opt in, push changes back to AT so a staged migration off AT is possible without ever leaving customers in the dark.

Feature summary

Pull direction (on by default when connected):

  • Companies, Contacts, Tickets, Ticket Notes, Time Entries, Contracts, Resources, Picklists, Attachments, Projects + Tasks, User-Defined Fields

  • AT-authored Quotes with customer-facing accept/reject portal (opt-in)

  • Automatic hourly attachment sweep + daily full re-sync at 03:30

Push direction (each surface is an opt-in toggle, defaults off):

ToggleWhat flows to AT
push_ticketsTicket create + sparse update (subject, body, status, priority, queue, assignee)
push_repliesStaff ticket replies as TicketNotes (audit-trail sync)
push_time_entriesTime entries as AT TimeEntries (enables the customer-reply email swap)
push_attachmentsTicket files as AT TicketAttachments
push_quotesconcorbit-authored Deal quotes up to AT as Quote + QuoteItems
push_contract_adjustmentsApplied contract-line quantity changes as AT ContractServiceAdjustments (signed delta). Off when AT is the system of record for contract quantity changes.

Automation + tooling:

  • Portal quote accept/reject → writes back approvalStatus in AT

  • Accepted quotes optionally auto-convert to a concorbit draft Invoice (auto_convert_accepted_quotes)

  • Sync conflict detection + review queue on /settings/autotask/conflicts

  • Inbound email MX swap wizard (/settings/email/inbound) for the final migration step

  • Live API health monitoring on /admin/platform/api-health (super-admin only): call counts, error rates, 429 hits, average latency

  • Encrypted-at-rest credentials, audit log of every connection + sync event

The migration story

Most MSPs landing here want to stop using AT eventually. The whole integration is shaped to make that a weeks-not-days project you control rather than a big-bang cutover. Read-only pull first, then each push surface one at a time, then finally the inbound email swap. See Staged migration off Autotask at the bottom for the full playbook.

Billing modes and governance

The Autotask integration has two axes that decide who is in charge of a customer's billing at any moment: governance (who owns the billing logic) and billing mode (who generates the actual invoice the customer pays). They are deliberately separate so that concorbit can prove out its billing for weeks against a real AT invoice stream before any customer-facing change.

Governance lives on each support contract (support_contracts.governance, either autotask or concorbit). It decides who resolves overage, who runs the service-change pipeline, and who emits recurring service charges. Flipping a contract to concorbit-governed is the first commitment, see Governance.

Billing mode lives on companies and support contracts (autotask_billing_mode, either at_mirrored or concorbit_native). It decides who generates the invoice:

  • at_mirrored: Autotask generates the invoice the customer pays. concorbit mirrors it and computes billing parity against it on demand, as evidence for the eventual flip. This is the default for any AT-linked scope.

  • concorbit_native: concorbit generates the real invoice. Reached only via a deliberate flip on the Cutover tab.

Invoice governance (invoices.governance) is the per-invoice marker captured at creation:

  • concorbit: concorbit owns the lifecycle (autopay, dunning, the £0 paid lifecycle, dispute freezes).

  • autotask: AT owns the mutable state. Every reconciler pass hard-sets status from AT's flags.

  • quickbooks: QuickBooks owns it. AT wins over QB on co-linked invoices.

The flow from AT into concorbit is: AT-issued invoice, AutotaskInvoiceSync upserts it as governance=autotask, BillingItems become concorbit Charge rows linked back to the contract line, and BillingParityService computes on demand what concorbit would have billed for those charges against what AT actually billed, feeding the Cutover tab's green-streak gate. When a company is later flipped to concorbit_native via the Cutover tab, AT's invoice sync skips that company permanently and concorbit's billing run generates the customer's invoice instead.

See Governance, Billing parity, Cutover, and Wind-down for the per-stage detail.

Prerequisites

You need an Autotask API user (not a regular user) with:

  • A user role that can read Tickets, Contacts, Companies, Contracts, Resources, TimeEntries (and write them once push lands)

  • An API Tracking Identifier (the integration code), one per integration, generated in Autotask under Admin → Resources → Integrations → API Tracking Identifiers

Your Autotask administrator creates these. concorbit never asks for a regular user's password.

Connecting

  1. Open Settings → Autotask in concorbit (or /settings/autotask directly).

  2. Paste in the API user's email, secret, and the integration code.

  3. Click Connect. concorbit:

    • looks up your Autotask zone (~1s),

    • validates the credentials with a tiny test query (~1s),

    • saves the encrypted credentials and marks the connection healthy.

  4. If anything fails, the form tells you which step, usually a typo in the email or wrong integration code.

The page then shows the connection status, last sync time, and the recent activity log.

Disconnecting

Click Disconnect. The connection is deactivated immediately, no more sync activity, but your existing concorbit data stays. Reconnecting later overwrites the deactivated row.

What's tracked in API health

Every Autotask API call increments per-provider Redis counters that feed the platform's /admin/platform/api-health page (super-admin only). For the autotask provider you can see:

  • Calls in the last hour and 24 hours

  • Errors (any non-2xx response)

  • Rate-limit (429) hits. Autotask aggressively throttles; this number tells you when sync feels slow

  • Average call duration

  • Circuit-breaker state (closed = healthy, open = too many failures, paused for cooldown)

  • Per-tenant call volume drilldown when you click into the provider

There's no per-tenant rate-limit cap on Autotask. The rps: 5 in config/rate_limits.php is platform-wide because each concorbit tenant talks to its own Autotask org. If a tenant complains about slow sync, check 429 hits first.

Targeted sync (single company)

Most of the time you don't need to think about sync, the scheduled sweep picks up changes within 15 minutes. But sometimes you want to refresh one company right now:

  • An admin just updated a contact in Autotask and you need it in concorbit before a ticket handover.

  • You've onboarded a new customer in Autotask and want their ticket history pulled immediately.

  • You manually linked a concorbit company to an Autotask company in Mapping Master and want the initial backfill without waiting.

Two ways to trigger this:

Via the UI: open any company that has an Autotask badge in concorbit, click the ↻ Sync from AT button next to the badge. concorbit pulls that company + its contacts, tickets, and contracts in the background. Returns to the company page with a status message when finished.

Via the CLI, for batch/scripted operations:

php artisan autotask:sync --entity=all --company=<concorbit-company-uuid>

This runs the same targeted pull as the UI button. Add --tenant=<tenant-uuid> to pick a specific tenant when you're on the platform-admin side.

Targeting specific Autotask records

If you know the exact Autotask record ids you want to refresh (from a support case number or from comparing concorbit to Autotask), you can target them directly:

# Pull just these three Autotask tickets
php artisan autotask:sync --entity=tickets --ids=12345,12346,12347

# Re-sync one Autotask company by its AT id
php artisan autotask:sync --entity=companies --ids=542

--ids works for companies, contacts, tickets, and contracts, the entities with stable external ids.

What's skipped in targeted mode

When you use --company, concorbit skips time-entries, contract-lines, adjustments, blocks, and project-tasks. These scope via their parent ticket / contract / project in Autotask and can't be filtered by company in a single API call, so they'll catch up on the daily full sweep. The scheduled --entity=all sync handles them on the full tenant.

Projects + Tasks

concorbit syncs Autotask Projects (and their project-level Tasks, distinct from helpdesk tickets) into the Projects module every 30 minutes:

  • Projects: name, description, status, start / end dates, and the project lead (mapped to a concorbit user via the Autotask Resource mapping). Parent company must already be linked; unlinked-parent projects are skipped with a warning in the sync log.

  • Project tasks: title, description, status, priority, assigned user, due date, estimated hours. Parent project must exist; tasks whose project isn't yet synced will pick up on the next tick.

Autotask project / task statuses and priorities need mapping the first time you connect, see Settings → Integrations → Mappings → Code lists → Autotask:

  • project_status: AT project status id → one of planning, active, on_hold, completed, cancelled. Unmapped values soft-default to planning so the project still imports.

  • task_status: AT task status id → one of todo, in_progress, in_review, done, cancelled.

  • task_priority: AT task priority id → one of low, medium, high, urgent.

Targeted pulls work on projects just like other company-scoped entities:

# Just this company's projects
php artisan autotask:sync --entity=projects --company=<concorbit-uuid>

# Specific AT projects by id
php artisan autotask:sync --entity=projects --ids=500,501

Project tasks don't accept --company because AT filters them by projectID, not company, so the daily full sweep picks them up.

Attachments

concorbit pulls Autotask ticket attachments into the Files module automatically:

  • Inline pull: when a ticket is pulled (every 5 min) its attachments are pulled too, so new support tickets look visually complete from the first sync.

  • Hourly sweep: the autotask:sync --entity=attachments cron iterates every linked ticket and re-checks for attachments added AFTER the ticket itself synced (the classic "engineer uploads a screenshot 20 min after creating the ticket" case).

Each concorbit File row records the AT attachment id in external_id and external_provider='autotask', so re-pulls are idempotent. Once an attachment is synced it's a no-op on every subsequent pass.

URL-typed attachments (AT's attachmentType == 'URL', links to external docs like Google Drive or SharePoint) are stored as storage_type='linked' File rows with the external_url preserved and no local bytes. The concorbit UI renders them as external links.

Attachment storage uses the tenant's default filesystem disk under tenants/{tenantId}/autotask/ticket-attachments/. No per-attachment virus scan runs on AT pulls, so the virus_scanned flag stays false, which the Files module surfaces as an unscanned badge.

Manual attachment re-sync for one tenant:

php artisan autotask:sync --entity=attachments --tenant=<tenant-uuid>

User-defined fields

Autotask supports admin-configured user-defined fields (UDFs) on every major entity. concorbit captures these alongside the regular picklist cache:

  • UDF definitions cache automatically when --entity=picklists runs (daily at 03:30 as part of --entity=all). Covers Tickets, Companies, Contacts, Projects. You can see every defined UDF on Settings → Integrations → Mappings → Code lists → Autotask, under a userDefinedFields sub-tab per entity.

  • Per-record UDF values come through the regular entity sync. AT includes them in the record payload under userDefinedFields. concorbit flattens them into the external_links.external_data.udf JSONB map so the Mapping Master and future reporting surfaces can read them without re-parsing AT's wire format.

For now UDFs are a read-only cache, so no concorbit UI renders them on ticket / company detail pages yet. A later session will surface UDF values alongside the existing integration badge.

Pushing ticket changes back to Autotask

By default concorbit only reads from Autotask, so the integration is one-directional. Push tickets (Settings → Autotask → Push tickets card) reverses that: when you edit a ticket in concorbit, the change syncs back to AT.

What gets pushed:

  • Create: a concorbit ticket with an AT-linked parent company becomes a new AT ticket (POST /Tickets); the returned AT id is saved so subsequent edits update that same ticket.

  • Update: edits to subject, body, status, priority, queue, or assigned_to trigger a sparse PATCH. Cosmetic fields (view counts, read-status flags) don't fire a push.

  • Idempotent: re-dispatches on the same state hit AT's PATCH endpoint which accepts missing keys as "don't touch", so AT-side edits survive.

Before turning it on

Push tickets requires three things to be in place. The UI won't force you through a checklist, but if any are missing the push will silently log-and-skip rather than break the UI:

  1. Autotask connected and active.

  2. Every company with a ticket to push is AT-linked (via the Mapping Master's Companies tab). AT requires a companyID on every ticket, so concorbit refuses to guess which AT company the ticket belongs to.

  3. Status and priority mappings work in both directions (Mapping Master → Code lists → Autotask). If you've mapped AT status 5 → concorbit open, concorbit will also translate open → 5 on push. The push refuses when a ticket has a status that has no reverse mapping.

Safety notes

  • Starts OFF. Flipping it on shows a confirmation modal explaining the blast radius.

  • Doesn't queue up a back-fill: existing tickets created before push was enabled stay local-only until you manually edit them, which creates the AT record.

  • Autotask's 10 req/sec rate limit applies. Push jobs queue with circuit-breaker + exponential backoff so a burst of ticket edits won't trip AT's throttle.

  • If you disable and re-enable later, nothing replays. Only edits while the setting is on hit AT.

  • The pull direction is safe: when concorbit pulls a ticket FROM AT and writes it locally, the push observer is suppressed so you don't get a loop.

Pushing replies (audit-trail sync)

When Push replies is on in Settings → Autotask, every staff ticket reply you post in concorbit also lands in Autotask as a TicketNote. Customer-authored replies (arriving via concorbit's inbound email pipeline) never push. AT either has its own inbound pipe seeing the same mail, or the tenant has already swapped inbound over.

What gets pushed

Reply typePushed to AT?AT noteTypeAT publish
Staff internal noteYes1 (internal)false
Staff public replyYes2 (public)false
Customer-authored reply (inbound email)Non/an/a

The customer-email direction

concorbit always sends the customer reply email via TicketReplyMail through the tenant's SMTP. Replies thread back via a token in the Reply-To header. That's consistent with push_replies=on or push_replies=off.

Why concorbit owns the customer email on the reply-thread surface and not AT: in Autotask's real workflow the customer email from a reply fires from a TimeEntry (where staff check "reply to ticket contact" alongside logging billable time), not from a TicketNote. TicketNotes are the audit surface. The staged-migration toggle for who-owns-the-customer-email will land on the time-entry path (a later session).

So Session 7 is an audit-trail sync: AT's ticket portal shows the same reply history concorbit does, useful if any AT admins still read tickets there, but the customer conversation lives in concorbit's email pipeline throughout.

Pushing time entries, and choosing who emails the customer

Time entries are where the real bidirectional story lives. In Autotask's workflow, when an engineer logs time against a ticket they get a "reply to ticket contact" checkbox on the time-entry form. If it's ticked, the summaryNotes (what the engineer typed) becomes the customer's email. That's the actual customer-reply mechanism in AT, not TicketNotes.

concorbit's time-entry form mirrors this: a Reply to contact checkbox on the form, and a single toggle in settings that decides who sends that email.

Two settings under Settings → Autotask:

SettingDefaultEffect
push_time_entriesoffWhen on, every time entry you log in concorbit pushes to AT as a TimeEntry.
reply_email_sourceconcorbitOnly meaningful when both push_time_entries is on AND the engineer ticked "Reply to contact" on the form. concorbit: concorbit sends TicketTimeEntryReplyMail to the contact, AT records the entry silently. autotask: concorbit suppresses its own email, AT fires its notification rules via publishNotes=true.

The staged migration off Autotask:

  1. Read-only mirror. push_time_entries=off. concorbit reads AT; nothing flows back. Team can try concorbit alongside AT.

  2. Team logs time in concorbit; AT still emails customers. push_time_entries=on, reply_email_source=autotask. Engineers work in concorbit (log time, tick "Reply to contact"), AT's templates/signatures/portal links reach the customer. Nothing visibly changes for customers.

  3. Email templates ported; concorbit emails customers. Flip reply_email_source=concorbit. AT keeps recording time entries silently for audit / any AT admins still looking. Customer sees concorbit's email now. Before flipping, set up inbound email at /settings/email/inbound so customer replies land back into concorbit. The page now shows an Inbound verified / Inbound not verified badge next to this toggle and confirms before letting you flip without verification.

  4. AT off. Disable the connection. concorbit was already doing everything. Inbound mail to support@yourdomain.com already routes through concorbit (set up in step 3), so your old PSA inbox can be retired.

Each phase can sit indefinitely, there's no forced path. The typical migration is weeks-to-months on phase 2 (proving concorbit keeps the team productive) and phase 3 is a day or two once templates are ready.

What gets pushed

Time entry statePushed?AT summaryNotesAT internalNotesAT publishNotesconcorbit emails?
notify_contact=falseYesfrom descriptionfrom internal_notesfalseNo
notify_contact=true, source=autotaskYesfrom descriptionfrom internal_notestrue (AT emails)No (suppressed)
notify_contact=true, source=concorbitYesfrom descriptionfrom internal_notesfalseYes
notify_contact=true, push_time_entries=offNo (not pushed)n/an/an/aYes (fallback)

Required mappings

Time-entry push refuses when:

  • Parent ticket isn't AT-linked (nothing to attach the entry to)

  • The engineer logging the time isn't mapped to an AT resource. Configure under Settings → Integrations → Mappings → Code lists → Autotask → Resources

If either is missing, the entry is created locally and logged as "refused" in the sync log; fix the mapping and the next edit to the entry will retry.

Secondary recipients

The UI's Reply to contact is the headline checkbox because that's the 95% case. For the rare entry where you also want to notify the assignee, all ticket watchers, or a custom CC list, the More recipients… expandable on the time-entry form exposes:

  • Notify assignee: emails the ticket's assigned_to user in addition to the contact.

  • Notify all resources: AT-side flag mapping (equivalent to AT's "Notify all resources on ticket").

  • CC emails: arbitrary addresses separated by comma.

These don't change the AT / concorbit routing decision; they add recipients on top of the primary "Reply to contact" route.

Pushing attachments

Attachment push mirrors the Session 5 pull: when a file is attached to an AT-linked ticket in concorbit, concorbit pushes it up to AT as a TicketAttachment. Customers who look at the ticket in AT's portal see the same files your team uploaded in concorbit.

Settings → Autotask → Push attachments (default off). Once on, the push fires on every File::created event where:

  • fileable_type is a Ticket (Company / MediaFolder / other attachments are out of scope; AT uses separate endpoints per parent type and Session 9 only covers tickets)

  • The file isn't already AT-sourced (external_provider='autotask' short-circuits to prevent echo-push of Session 5 pulled files)

  • The parent ticket has an AT external link

Bytes: uploaded files POST as base64-encoded data. AT caps single-attachment uploads at 6MB; oversized files refuse with a domain log entry; chunked upload isn't implemented (contact support if you genuinely have ticket attachments over 6MB).

Linked files: M365 / OneDrive references stored in concorbit as storage_type='linked' push with attachmentType='URL' and fullPath set to the external URL. No bytes move; AT renders them as clickable links in the attachment list.

Idempotency: once pushed, the File row's external_provider='autotask' + external_id=<AT id> columns are written. Re-dispatches (retry after 429) return the existing AT id without re-uploading.

Manual attachment re-sync still works (the pull direction stays, --entity=attachments) and won't conflict with the push: AT-sourced File rows are marked up-front, so they don't round-trip.

Pulling quotes + customer-facing portal

When a sales engineer builds a quote in Autotask and is ready to share it with the customer, concorbit can pull that quote and host a branded accept/reject portal for the customer, and their decision flows back to AT automatically.

Enable Pull quotes under Settings → Autotask (default off, since most tenants don't have AT Quotes in their workflow, so the pull is opt-in to avoid syncing tables no one uses).

How it works

  1. Admin creates the quote in AT. concorbit's autotask:sync --entity=quotes pulls it on the next tick (or daily as part of --entity=all).

  2. Admin finds the portal link. Settings → Autotask → Quotes lists every pulled quote with a per-row portal URL (/quote/at/{token}). Copy-paste it to the customer via whatever channel you usually use. (Auto-emailing the link from concorbit is a future session; for now the admin triggers it manually.)

  3. Customer clicks the link. No login required, the token is the auth. Throttled to 30 requests/minute to slow token brute-forcing.

  4. Customer accepts or rejects. The decision is recorded in concorbit (accepted_at / rejected_at) AND a background job PATCHes the AT Quote with approvalStatus=Approved/Rejected + the decision timestamp.

  5. AT admins see the decision. Next time they look at the quote in AT, the status reflects the customer's choice.

Idempotency + safety

  • Portal links are 64-char random tokens generated on first pull and preserved across subsequent pulls. Shared links stay valid.

  • Each token decides exactly once: a second visit to /accept or /reject after a decision returns HTTP 409.

  • Expired quotes (past expirationDate) flip to expired on first portal visit and refuse decisions with HTTP 410.

  • Decision push-back is retried with exponential backoff if AT is down. Once local state is accepted / rejected, the AT sync is guaranteed eventually (ApiJob circuit breaker / fair-use apply).

Admins used to copy-paste portal links manually. As of the polish pass, Settings → Autotask → Quotes → detail page gains a Send to contact button next to the Copy link. Clicking it emails the customer through your tenant SMTP using the SYSTEM_QUOTE_SIGNING system email template (the same one concorbit-authored proposals use, so both flows render consistently). An EmailLog row is written for audit.

The button is disabled when the quote has no contact email. You'll see "no contact email" surfaced on the row so the fix is an actionable Mapping Master link, not a silent failure.

Auto-convert accepted quotes to Invoices

Settings → Autotask → Auto-convert accepted quotes (default off). When on: a customer accepting an AT-authored quote in the portal (or an AT admin flipping approvalStatus to accepted on AT-side) triggers concorbit to spin up a draft Invoice with the quote's line items pre-populated and the company + contact linked.

  • Invoice lands as draft (not sent) so admin reviews + tweaks before sending, with no accidental "customer gets invoice without opening it first".

  • Idempotent: the quote is stamped converted_at + converted_to_type='invoice' + converted_to_id=<invoice uuid> on success. A second trigger no-ops and returns the existing invoice.

  • Manual alternative: on Settings → Autotask → Quotes → detail, there's a Convert to invoice button that performs the same conversion on-demand. Button flips to Open invoice once converted, so a second click navigates to the draft rather than creating duplicates.

  • Refuses when: quote isn't accepted, company isn't linked in Mapping Master, or the quote has no line items; each comes back with a specific error message on the settings page.

Scope note: the MVP converts only to Invoice. SupportContract conversion for recurring lines is deferred because it needs its own UX around contract type / start-end / rate overrides, and the invoice path covers the one-time-billable case that's most common.

Pushing concorbit-authored quotes to Autotask

The Session 10 quote pull is one direction only: AT authors, concorbit hosts the portal. When concorbit is the system of record for sales (via the Deals module's quote builder) you want the reverse: concorbit authors the quote and AT's ops team gets a mirrored AT Quote + QuoteItems so their downstream workflows (contract generation, project spin-up, invoicing if you haven't migrated yet) stay tied in.

Settings → Autotask → Push quotes (default off, opt-in so existing pull-only workflows don't start bidirectionally syncing without admin intent).

How it works

  1. Build the quote in concorbit on a Deal (see the CRM → Deals docs for the quote builder).

  2. On the Deal detail page, click Push to Autotask. The button flips to Update in Autotask once the quote exists in AT.

  3. concorbit POSTs /Quotes with company, contact (if linked), name, description, 30-day default expiration, and externalQuoteNumber=CCB-{deal.id} so AT admins can cross-reference. Then N POSTs to /QuoteItems for each line.

  4. Line-type inference: billing_type=recurring → AT lineType 1 (Service), billing_type=one_time → AT lineType 3 (Product). Override by editing the line after push if AT's Service Bundle picker needs a specific type.

Re-push

The button becomes Update in Autotask after the first push. Clicking it sparse-PATCHes the AT Quote header (name, description, externalQuoteNumber) but leaves line items alone. AT line-item ids don't reconcile cleanly across edits (matches the QB bill behaviour), so you'd otherwise get phantom deletes + re-creates. If you need to change lines after push, edit them in AT.

Preconditions

Push quotes refuses, with an actionable error message rather than silent failure, when:

  • The setting is off

  • The Deal's company isn't AT-linked (Mapping Master → Companies)

  • The Deal has no quote lines built

Line-item failures during push log individually but don't void the header. The AT Quote is already there; admin can retry the failed line.

Legacy Session 10 scope note

Everything the original Session 10 shipped is still in place. What Session 10 deferred has since landed:

  • Auto-email the portal link: shipped in the polish pass (see "Sending the portal link from concorbit" above).

  • concorbit-authored quotes pushed up to AT: shipped (see "Pushing concorbit-authored quotes to Autotask" above).

  • Auto-convert accepted quote → Invoice: shipped (see "Auto-convert accepted quotes to Invoices" above). SupportContract conversion remains deferred.

Conflict review

When both sides edit the same field between syncs, the last write wins silently. That's how Sessions 1-10 work by design, because blocking syncs on disagreement would create a worse failure mode than accepting the divergence.

Session 11 adds detection + a review queue so admins see divergences and decide whose value to keep.

How a conflict is detected

On every ticket pull, the sync compares three values per field:

  1. Baseline: what we wrote to concorbit on the previous pull (cached on the external_link's external_data.baseline_* keys).

  2. Local now: what concorbit currently has (may have been edited locally since the baseline).

  3. External now: what this pull received from AT.

A conflict is recorded only when ALL of the following are true:

  • Local moved AWAY from the baseline (concorbit edited it)

  • External moved AWAY from the baseline (AT also edited it)

  • Local and external landed on DIFFERENT values (i.e., didn't independently agree)

Single-sided edits and coincidental agreement are regular sync updates, not conflicts.

Currently watched fields on tickets: subject, status, priority, assigned_to. Extending to other entities follows the same pattern: one detection call per pull.

The review queue

Settings → Autotask → Conflicts: paginated list of open conflicts with filters by resolution state. Per-row actions:

  • Keep concorbit → dispatches a push job to overwrite AT with concorbit's current value. The conflict flips to concorbit_wins.

  • Accept AT → the pull already applied AT's value; this just marks the row reviewed (external_wins).

  • Ignore → audit-only acknowledgement. Useful when the divergence no longer matters (e.g. the ticket has since been closed).

Resolving records who resolved it, when, and optional notes. The history is preserved even after the row leaves the "unresolved" queue.

Important

  • Pulls don't block on conflicts: they always apply the external value (last-write-wins stays in place). The conflict row is an audit trail, not a gate.

  • Already-resolved conflicts don't re-resolve: hitting the resolve endpoint on a resolved row returns an error. New divergences on the same field create a new conflict row (resolutions are their own audit).

  • Permission-gated on autotask.manage: the review queue is an admin surface, not something every staff user should touch.

Staged migration off Autotask

Most tenants connecting Autotask to concorbit want to eventually stop using AT, but the cost of getting the cutover wrong (customers emailing an inbox nobody reads, engineers working on outdated ticket state, invoices billed twice) is high enough that a big-bang flip is the wrong approach. The phases below are the playbook: each can sit indefinitely, and any can be rolled back by flipping its toggle off.

Phase 1: Read-only mirror

  • Connect Autotask (see Connecting)

  • push_tickets, push_replies, push_time_entries, push_attachments, push_quotes: all OFF

  • Result: concorbit reads everything from AT (tickets, companies, contacts, time entries, contracts, quotes, attachments, projects). Nothing flows back. Team can try concorbit alongside AT without risk.

Typical duration: days to weeks. You're proving that concorbit surfaces the same data AT does, with the same mappings.

Phase 2: Team works in concorbit, AT still owns customer email

  • Turn on push_tickets, push_replies, push_time_entries

  • Leave reply_email_source=autotask (the default)

  • Customers see zero change. AT's notification rules still fire the emails using your existing AT templates / signatures / portal links.

  • Engineers log tickets + time + replies in concorbit; AT stays in sync automatically.

Typical duration: weeks to months. This is where you prove concorbit keeps the team productive and any gaps are tolerable. If you need to roll back, flip the three push toggles off and everyone's back on AT with no data loss.

Phase 3: concorbit owns customer email

This is the step that feels biggest but is mostly preparation:

  1. Port your email templates. The customer-reply email that used to come out of AT now fires from concorbit's TicketTimeEntryReplyMail. Rebuild the templates you rely on (branding, signature, portal login link if you use one) under Settings → Email → Templates.

  2. Set up inbound email. Customer replies need somewhere to land. Open /settings/email/inbound and follow the Inbound email setup wizard: declare your support addresses, pick a routing path (forwarding rule recommended, direct MX advanced), send a test email, click Verify. The page shows a green Verified badge when routing is confirmed end-to-end.

  3. Flip reply_email_source=concorbit. Settings → Autotask → Customer reply email source. The toggle shows an Inbound verified / Inbound not verified pill; flipping to concorbit without verification triggers a hard confirmation so you can't drift into the broken state by accident.

  4. AT keeps recording time entries silently for audit / any AT admins still looking at the portal.

Typical duration: a day or two once templates are ready, and the inbound wizard is a 15-minute job.

Phase 4: Autotask off

  • Disable the Autotask connection on the settings page.

  • The customer-facing support@yourdomain.com address already routes through concorbit (set up in Phase 3), so the old AT inbox can be retired.

  • All historical data stays in concorbit because it was pulled throughout.

You can stay in Phase 2 or Phase 3 indefinitely, with no forced path to Phase 4. Lots of MSPs end up living in Phase 3 long-term because AT still hosts useful admin ops (complex billing runs, vendor price lists) without any customer-facing surface area.

Troubleshooting

"Could not find an Autotask zone for this email"
The email doesn't match a registered Autotask user. Double-check it: a typo is the most common cause. Autotask returns HTTP 500 for unknown users (yes, really), which concorbit treats as "user not found".

"Autotask rejected the credentials"
Zone discovery worked, but the secret or integration code is wrong. Generate a fresh integration code in Autotask and try again.

Sync feels slow / 429 hits climbing
Check /admin/platform/api-health. Autotask's hard rate limit isn't published; we observe ~10 RPS as the soft ceiling. concorbit defaults to 5 RPS to stay well clear. If you're seeing sustained 429s, contact Autotask support: your account might be on a lower tier than usual.

Connection went red after working for weeks
Usually a rotated integration code in Autotask, or a security policy that disabled the API user. Check Autotask's audit log; reconnect with fresh credentials.