FixControl/Documentation

Admin Guide

Issue trackers (Jira, Linear, Freshdesk, Odoo, TOPdesk)

Mirror approved FixControl issues to Jira, Linear, Freshdesk, Odoo Helpdesk, or TOPdesk so engineers, PMs, and support agents see them in their normal workflow.

When an issue is approved in FixControl, you can have it mirrored to your existing tracker. The link is bidirectional — comments and status changes flow both ways — so the tracker stays the source of truth for execution while FixControl stays the source of truth for intake and patch flow.

Five trackers are supported today: Jira (Atlassian Cloud and Server), Linear, Freshdesk (support desk), Odoo Helpdesk (odoo.com), and TOPdesk. Freshdesk, Odoo, and TOPdesk are implemented through the same provider abstraction as Jira and Linear, so everything below about mirroring applies — they just add a support-specific layer (related-ticket history, governed customer replies, and — for Freshdesk — KB harvesting) on top.

Jira

Auth choices

  • OAuth (3LO) — recommended for Atlassian Cloud. Site admin consents once; FixControl gets per-tenant tokens with refresh.
  • API token + email — works for both Cloud and self-hosted Data Center / Server.

OAuth setup

  1. In your Atlassian developer console (developer.atlassian.com): My apps → Create → OAuth 2.0 (3LO).
  2. Permissions (scopes): read:jira-work, write:jira-work, read:jira-user, manage:jira-project, offline_access.
  3. Callback URL: https://<your-host>/api/auth/jira/callback.
  4. Copy the Client ID and Secret and provide them to FixControl during the connect flow.
  5. Tenant admin clicks Settings → Integrations → Jira → Connect, picks the Atlassian site, approves.
  6. After consent: pick a default project and issue type. New mirrored issues go there unless overridden.

API-token setup

  1. In Atlassian: Profile → Security → API tokens → Create.
  2. In FixControl: Settings → Integrations → Jira → Connect via API token, paste base URL (e.g. https://acme.atlassian.net), email, and the token.

What gets mirrored

  • Issue title and description (rendered as Atlassian Document Format).
  • Priority (mapped from FixControl critical/high/medium/low to Jira priority scheme).
  • Labels (fixcontrol, plus any FixControl tags).
  • Status changes — when FixControl moves the issue to done, the Jira issue transitions to your configured "done" status.
  • Comments — bidirectional.

The FixControl issue shows a deep link to the Jira issue. The Jira issue keeps a [FixControl: <id>] reference in the description.

Field mapping

The default mapping covers most teams. If you need custom fields populated (epic link, story points, custom enums), edit the field map in Settings → Integrations → Jira → Field mapping. Mapping is per-tenant — different tenants can map to different schemas.

Jira field-mapping editor with priority and status mapped to FixControl's enums
Jira field-mapping editor with priority and status mapped to FixControl's enums

Status sync back (inbound webhook)

Jira can push issue events to https://<your-fixcontrol-host>/api/integrations/jira/webhook. The endpoint authenticates on a per-tenant webhook token (a global JIRA_WEBHOOK_SECRET works only as a single-tenant fallback); without valid credentials it refuses. What it does is deliberately narrow, the same contract as the Freshdesk webhook:

  • It syncs only the issue's cached tracker status (the label the UI shows) — it never drives the FixControl workflow status.
  • A reopen (a done issue moving back to an open status) notifies your managers and adds a REOPENED flag on the issue — re-surface, never re-execute.
  • Each update refreshes the related-support-history cache so the next agent turn reads the live comment thread.
  • With the per-tenant setting JIRA_CAPTURE_LEARNING enabled (off by default), a transition to a done status captures the resolved case as project-scoped learning — PII-redacted, and only when the issue belongs to a project.

Resolved Jira work as evidence

For Jira tenants, triage pulls a small set of related resolved Jira issues as support-context evidence — each with why it matched and a trust score, evidence to weigh rather than an authoritative answer. On Jira Service Management, internal comments are excluded from extracted "resolutions" so internal text never leaks into agent context. The per-tenant toggle JIRA_INGEST_CONVERSATION (off by default) additionally feeds the linked issue's recent comment thread into the AI context, PII-scrubbed, with JSM-internal comments marked as notes — read-direction only.

Linear

Auth choices

  • OAuth — recommended for teams.
  • API key — per-user, simpler.

OAuth setup

  1. In Linear: Settings → API → OAuth applications → New application.
  2. Redirect URI: https://<your-host>/api/auth/linear/callback.
  3. Scopes: read, write, issues:create. Public app: off.
  4. Copy the Client ID and Client secret and provide them to FixControl during the connect flow.
  5. Tenant admin clicks Settings → Integrations → Linear → Connect.
  6. Pick a default team. Mirrored issues go there unless overridden.
Linear team picker shown after OAuth consent, listing the tenant's available teams
Linear team picker shown after OAuth consent, listing the tenant's available teams

API-key setup

  1. In Linear: Settings → API → Personal API keys → New key.
  2. In FixControl: Settings → Integrations → Linear → Connect via API key, paste the key.

What gets mirrored

Same fields as Jira: title, description (Markdown), priority (mapped to Linear's 04), labels, status, comments. Statuses map to Linear's workflow states (started, completed, canceled, etc.).

Freshdesk

Freshdesk is FixControl's first support-desk tracker. It plays two roles at once — the ticket system FixControl mirrors issues to, and a source of support knowledge FixControl learns from — while Freshdesk stays the system of record for the customer conversation.

Setup

  1. In Freshdesk: Profile → Settings → Your API key (per-agent) — copy it.
  2. In FixControl: Settings → Integrations → Freshdesk → Connect, then provide:
FieldNotes
Freshdesk domain URLe.g. https://acme.freshdesk.com. The domain is the tenant — there's no shared default.
API keyEncrypted at rest. FixControl uses it as the HTTP Basic username.
Default requester emailFreshdesk requires a requester on every new ticket; FixControl uses this address.
Default group id _(optional)_New tickets land in this Freshdesk group.

In production the domain must be https and a public host. Auth failures (401/403/404) flip the connection to reconnect_required; transient 429/5xx are retried.

What gets mirrored

  • Title → subject, body → description (converted to safe HTML), priority mapped to Freshdesk's 1–4 scale, kind (BUG/FEATURE) to the Freshdesk ticket type.
  • A deep link both ways: the FixControl issue stores the ticket id + URL; the ticket carries the FixControl key (and, when the account's scheme allows, custom fields cf_fixcontrol_key / cf_fixcontrol_url).
  • A consistent tag taxonomy so future retrieval can match by classification, not keywords: fixcontrol (provenance), fc-severity-<priority>, fc-type-<bug|feature>, fc-domain-<domain> (risk domains), fc-system-<system> (affected components, classified automatically), and fc-source-slack when the ticket came from Slack intake. Customer tags are preserved; email/phone-shaped tokens are never tagged.

Related support history (evidence)

When FixControl triages a new issue for a Freshdesk tenant, it pulls a small, scoped set of related past tickets — preferring the fc-* tags, falling back to derived search terms, then recent resolved tickets. Each surfaced ticket explains why it matched and carries a trust score (resolved + recent + substantive resolutions rank above stale or unresolved ones), so an operator or agent sees "we've handled this before, here's how it went" — as evidence to weigh, never an authoritative answer that bypasses review. This history is a separate evidence source from the docs knowledge base; the two are never blended.

Resolved tickets → KB drafts

When a ticket is resolved (status Resolved/Closed) — automatically when a mirrored FixControl issue moves to done, or manually from the suggestions surface — FixControl can mine the agent's public resolution reply into a pending knowledge-base draft. Quality gates run before any of this (too-short / closing-note-only / generic resolutions are skipped), it dedupes against existing knowledge, and nothing is ever published automatically — a human approves the draft first. Customer support content never silently becomes published KB.

Status sync back (inbound webhook)

By default FixControl reads a ticket's status on demand. To have Freshdesk push updates instead — so the FixControl issue shows the live support status and a resolve triggers the KB harvest the moment it happens — configure an inbound webhook:

  1. Set FRESHDESK_WEBHOOK_SECRET on the FixControl deployment (a long random string). Without it the endpoint is disabled (returns 503) — it never runs open.
  2. In Freshdesk, add an Automation rule ("Ticket is updated" → action "Trigger webhook") that POSTs to https://<your-fixcontrol-host>/api/integrations/freshdesk/webhook with a JSON body carrying the ticket id and status, e.g. { "ticket_id": "{{ticket.id}}", "status": "{{ticket.status}}", "secret": "<the secret>" }. The secret may instead travel in the x-freshdesk-webhook-secret header.

What it does — and deliberately does not — do:

  • It syncs only the issue's cached tracker status (the label the UI shows). It never drives the FixControl workflow status: a support agent resolving a ticket cannot push the issue to done and bypass governance.
  • On a resolve/close it fires the same human-approved KB harvest described above (still pending, still de-duplicated, never auto-published).
  • Fresher context. Each update refreshes the related-support-history cache for that issue, so the next agent turn reads the live ticket conversation (a new customer reply or agent note) instead of a stale snapshot.
  • Reopen → re-surface, not re-execute. If a resolved/closed ticket goes back to open, FixControl notifies the tenant's managers (an issue needs review notification, deep-linked to the issue) and adds a REOPENED flag so it stands out in the list. It does not re-run agents or re-open the governed workflow — it asks a human to look.
  • On the timeline. Resolve and reopen show up as milestones on the mission's operational timeline (alongside intake → plan → approval → deploy), so the support side of the story is visible in the replay. Plain status changes don't add timeline noise.
  • The endpoint authenticates only via the shared secret (Freshdesk doesn't sign webhooks), constant-time compared; an unknown ticket id is acknowledged and ignored.

Progress notes on the ticket (outbound, opt-in)

With the per-tenant setting FRESHDESK_SYNC_PROGRESS_NOTES enabled (off by default), FixControl mirrors mission milestones onto the linked ticket as private notes — visible to support agents only, never to the customer:

  • Remediation plan approved — a fix is being prepared.
  • Patch ready — a patch was prepared and approved.
  • Fix applied — the change landed in the codebase.
  • Deployment approved — the fix is rolling out (including the rollout-promotion outcome when Argo Rollouts is wired).

Support sees remediation progress without leaving Freshdesk. The notes are strictly one-way and best-effort: a Freshdesk hiccup never blocks or rolls back a FixControl decision, rejections and internal review churn are not mirrored, and nothing about the notes touches the governed workflow. Because the notes are private (agent-only), no customer-facing communication happens — a public customer reply would go through the comms-governance approval flow instead, which this feature deliberately does not do.

Two-way sync — the full picture (all opt-in, all off by default)

Beyond the webhook and progress notes above, FixControl can sync more of the ticket lifecycle in both directions. Every sync is a separate per-tenant setting, off by default, and none of them can drive FixControl's governed workflow or post to the customer without human approval:

Inbound (Freshdesk → FixControl), via the same webhook:

  • FRESHDESK_INGEST_CONVERSATION — feed the ticket's recent conversation

(customer replies + agent notes, with emails/phone numbers redacted) into the AI context for the linked issue.

  • FRESHDESK_SYNC_PRIORITY_INBOUND — a ticket priority change updates the

issue's priority.

  • FRESHDESK_SYNC_TAGS_INBOUND + FRESHDESK_TAG_FLAG_ALLOWLIST — map ticket

tags to issue flags. Only tag=FLAG pairs you list ever map — a support-side tag can never set a governance flag you didn't allow.

  • FRESHDESK_CSAT_RESURFACE — a negative satisfaction rating notifies your

managers to take another look.

  • FRESHDESK_SYNC_MERGE_INBOUND — support merging the ticket marks the

FixControl issue as a duplicate of the primary ticket's issue.

  • FRESHDESK_SYNC_ASSIGNMENT_INBOUND + FRESHDESK_GROUP_ASSIGNEE_MAP

reassignment routes the issue (responder email directly, or via your group→assignee map).

  • FRESHDESK_SLA_RESURFACE — an (approaching) SLA breach notifies your

managers — like reopen: re-surface, never re-execute.

  • FRESHDESK_CAPTURE_LEARNING — a ticket resolved on the support side is

captured as project-scoped learning (PII-redacted; no project ⇒ no capture), so future work on the same project can surface it.

Outbound (FixControl → Freshdesk):

  • FRESHDESK_PUSH_STATUS_ON_RESOLVE — resolving the issue moves the ticket

to Pending (awaiting customer). Never Resolved/Closed — support owns closing the loop; tickets support already settled are left alone.

  • FRESHDESK_REOPEN_ON_REGRESSION — a rollback or failed production deploy

of the shipped fix reopens the ticket with a private note, so support knows the customer's issue is not resolved after all.

  • FRESHDESK_PUSH_PRIORITY_ESCALATION — a critical escalation on the

FixControl side raises the ticket to Urgent (never lowers a priority).

  • FRESHDESK_CUSTOMER_REPLY_ON_RESOLVE — resolving queues a customer-reply

draft in the communications review queue. Nothing is ever posted automatically: a human approves (and usually edits) the draft, and only then is it posted as a public reply on the ticket.

Boundaries

  • Slack never calls Freshdesk directly. The flow is always Slack → FixControl intake → FixControl issue → (optionally) Freshdesk ticket.
  • Kubernetes / Argo and deployment gates are unchanged. Nothing about a Freshdesk link relaxes an approval gate.
  • Support memory and the KB stay separate systems — retrieved ticket history is evidence; the KB is human-approved knowledge.

Odoo Helpdesk

Odoo Helpdesk (odoo.com) is supported the same way Freshdesk is: the helpdesk stays the system of record for the customer conversation, FixControl creates and links tickets, reads their stage back, and learns from resolved work — behind the same governance.

Setup

  1. In Odoo: Preferences → Account Security → API Keys — create a key.
  2. In FixControl: Settings → Integrations → Odoo → Connect, then provide:
FieldNotes
Odoo URLe.g. https://acme.odoo.com. FixControl talks JSON-RPC to this instance.
Database nameThe Odoo database (usually your subdomain name).
User login (email)The Odoo user the API key belongs to.
API keyEncrypted at rest.
Default helpdesk team id _(optional)_New tickets land in this helpdesk.team.

Helpdesk teams → projects

An Odoo helpdesk team can be bound to a FixControl project (binding type odoo_team, on the project's integration bindings). The binding routes tickets deterministically: when the inbound webhook sees a ticket whose issue has no project yet, it fills the project from the team binding — and only from the binding. FixControl never guesses a project in mirror context.

What gets mirrored

  • Title → ticket name, body → description (converted to safe HTML), priority mapped to Odoo's 0–3 scale (low … urgent), and the default team when configured.
  • After create, FixControl reads the ticket's stage back from Odoo — stages are free-form per team, so the label is never guessed.
  • A reference to the FixControl issue key travels in the ticket description; the FixControl issue stores the ticket id and deep link.
  • Odoo tags are not mirrored. Odoo tags (helpdesk.tag) are database records that must already exist before a ticket can reference them; FixControl does not create tag records on the fly, so FixControl labels do not become Odoo tags.

Outbound sync (opt-in, off by default)

  • ODOO_SYNC_PROGRESS_NOTES — posts FixControl engineering progress (patch ready, PR opened, …) as internal chatter notes on the linked ticket. Agents only, never the customer; strictly one-way and best-effort.
  • ODOO_PUSH_STATUS_ON_RESOLVE + ODOO_RESOLVE_STAGE — resolving the FixControl issue moves the ticket to the stage you name in ODOO_RESOLVE_STAGE (e.g. "Awaiting confirmation"), matched case-insensitively against the ticket's team stages. Because Odoo stages are free-form per team, the target stage is an explicit operator choice — with no stage configured nothing is pushed, and FixControl never guesses a stage. A ticket support already settled (solved/closed) is left alone.

Governed customer replies

With ODOO_CUSTOMER_REPLY_ON_RESOLVE enabled (off by default), resolving an issue that mirrors an Odoo ticket queues a customer-reply draft in the communication review queue — the same approval flow every AI customer communication uses. Nothing is ever posted automatically: a human approves the draft, and only then is it posted as a customer-visible chatter message (which Odoo e-mails to the ticket's followers). Reject or escalate posts nothing.

Status sync back (inbound webhook)

To have Odoo push ticket updates:

  1. Set ODOO_WEBHOOK_SECRET on the FixControl deployment (a long random string). Without it the endpoint is disabled (returns 503) — it never runs open.
  2. In Odoo, add an Automation rule on helpdesk.ticket with a Send Webhook Notification action that POSTs to https://<your-fixcontrol-host>/api/integrations/odoo/webhook. The secret travels in the x-odoo-webhook-secret header or the body's secret field, compared in constant time.

Odoo's standard webhook payload carries little more than the ticket id, so the payload is treated as a trigger, never the source of truth: FixControl reads the authoritative stage back from the Odoo API. The same deliberate limits as Freshdesk apply:

  • Only the issue's cached tracker status is synced — never the FixControl workflow status.
  • Reopen → re-surface, not re-execute. A settled ticket going back to an open stage notifies your managers and adds a REOPENED flag; no agents are re-run.
  • Each update refreshes the related-support-history cache so the next agent turn reads the live chatter instead of a stale snapshot.
  • With ODOO_CAPTURE_LEARNING enabled (off by default), a ticket reaching a settled stage is captured as project-scoped learning — PII-redacted; no project, no capture.

Related support history (evidence)

For Odoo tenants, triage pulls a small, scoped set of related settled tickets as evidence — each with why it matched and a trust score, to weigh rather than to trust blindly. Chatter messages are classified by their Odoo subtype: only customer-visible messages authored by the team ever count as a "resolution"; internal notes — and anything whose classification fails — are excluded, fail-closed, so internal notes can never leak into agent context as resolutions. The per-tenant toggle ODOO_INGEST_CONVERSATION (off by default) additionally feeds the linked ticket's recent chatter into the AI context, PII-scrubbed and with internal notes marked as notes — read-direction only.

TOPdesk

TOPdesk (SaaS, https://<name>.topdesk.net) is supported the same way Freshdesk and Odoo are: the service desk stays the system of record for the incident and the caller conversation, FixControl creates and links incidents, reads their status back, and learns from resolved work — behind the same governance.

Setup

  1. In TOPdesk, sign in as the operator FixControl should act as and create an application password: Operator menu → My Settings → Application passwords → Add. Copy it once; it is not shown again. Note the expiry date — TOPdesk defaults to one year, and an expired password makes the integration ask for a reconnect.
  2. Give that operator the permissions FixControl needs: API access, read/create/edit on first- and second-line incidents, and read on Supporting Files → Persons (used to resolve the caller).
  3. In FixControl: Settings → Integrations → TOPdesk → Connect, then provide:
FieldNotes
TOPdesk URLe.g. https://acme.topdesk.net. FixControl talks to the REST API at /tas/api on this instance.
Operator login nameThe login name of the operator the application password belongs to.
Application passwordEncrypted at rest. Not the operator's normal password.

FixControl verifies the connection by reading the current operator back from TOPdesk. A 401/403 is reported as an authentication failure that needs a reconnect, not as a temporary outage — that is usually an expired or revoked application password.

Operator groups → projects

A TOPdesk operator group can be bound to a FixControl project (binding type topdesk_operator_group, on the project's integration bindings). The binding routes incidents deterministically: when the inbound webhook sees an incident whose issue has no project yet, it fills the project from the group binding — and only from the binding. FixControl never guesses a project in mirror context.

What gets mirrored

  • Title → brief description (TOPdesk caps this field at 80 characters, so a longer title is shortened), body → request (converted to the limited HTML TOPdesk accepts), and the operator group from the binding when one is configured.
  • New incidents are created as first-line incidents.
  • The FixControl issue key travels in the incident's external number field; the FixControl issue stores the incident number (for example I 2607 001) and a deep link.
  • After create, FixControl reads the incident's processing status back from TOPdesk — status names are configured per instance, so the label is never guessed.
  • Priority and categories are not mirrored. TOPdesk priority, urgency, impact, category and subcategory are configuration records in your own instance; FixControl does not create or guess them. The FixControl priority stays readable in the incident request text.
  • Labels are not mirrored — a TOPdesk incident has no free-form label or tag field.

Outbound sync (opt-in, off by default)

  • TOPDESK_SYNC_PROGRESS_NOTES — posts FixControl engineering progress (patch ready, pull request opened, …) as incident actions marked invisible to the caller. Operators only, never the caller; strictly one-way and best-effort.
  • TOPDESK_PUSH_STATUS_ON_RESOLVE + TOPDESK_RESOLVE_STATUS — resolving the FixControl issue marks the incident completed. When you name a processing status in TOPDESK_RESOLVE_STATUS (for example "Afgemeld"), that status is set as well, matched case-insensitively against your instance's own status list. Because TOPdesk processing statuses are yours to configure, the target status is an explicit operator choice — with none configured only completed is written, and FixControl never guesses a status name. An incident the service desk already settled (completed or closed) is left alone.

Governed customer replies

With TOPDESK_CUSTOMER_REPLY_ON_RESOLVE enabled (off by default), resolving an issue that mirrors a TOPdesk incident queues a customer-reply draft in the communication review queue — the same approval flow every AI customer communication uses. Nothing is ever posted automatically: a human approves the draft, and only then is it posted as an incident action that is visible to the caller. Reject or escalate posts nothing. Internal notes and caller-visible replies travel through the same TOPdesk action stream and differ only in that visibility flag, which is exactly why the visible one exists only behind the approval flow.

Linking a customer

The incident's caller is what identifies the customer, the same role the requester plays on a Freshdesk ticket. Linking a customer to a TOPdesk-linked issue sets the caller on the incident.

Status sync back (inbound webhook)

TOPdesk has no webhooks that an application can register for you, so you configure the callback yourself:

  1. Set TOPDESK_WEBHOOK_SECRET on the FixControl deployment (a long random string). Without it the endpoint is disabled (returns 503) — it never runs open.
  2. In TOPdesk, go to Modules → Events & Actions, add an event on incident changes ("Edit card — Incident") and give it an HTTP action that POSTs to https://<your-fixcontrol-host>/api/integrations/topdesk/webhook, with the shared secret sent in the request header. Compare-in-constant-time is done on our side.

Because you author that payload yourself, FixControl treats it as a trigger, never the source of truth: it always reads the incident back from the TOPdesk API before acting. The same deliberate limits as Freshdesk and Odoo apply:

  • Only the issue's cached tracker status is synced — never the FixControl workflow status.
  • Reopen → re-surface, not re-execute. A settled incident going back to an open status notifies your managers and adds a REOPENED flag; no agents are re-run.
  • Each update refreshes the related-support-history cache so the next agent turn reads live incident actions instead of a stale snapshot.
  • With TOPDESK_CAPTURE_LEARNING enabled (off by default), an incident reaching a settled status is captured as project-scoped learning — PII-redacted; no project, no capture.

There is no scheduled polling: the webhook plus the read-back is the whole inbound route. Attachments are not synced.

Related support history (evidence)

For TOPdesk tenants, triage pulls a small, scoped set of related settled incidents as evidence — each with why it matched and a trust score, to weigh rather than to trust blindly. Incident actions are classified by their caller visibility: only actions visible to the caller can ever count as a "resolution"; actions marked invisible to the caller — and anything whose classification fails — are excluded, fail-closed, so internal notes can never leak into agent context as resolutions. The per-tenant toggle TOPDESK_INGEST_CONVERSATION (off by default) additionally feeds the linked incident's recent actions into the AI context, PII-scrubbed and with internal notes marked as notes — read-direction only.

Choosing where issues live

Issue detail with the Jira/Linear deep link visible in the header
Issue detail with the Jira/Linear deep link visible in the header

By default the FixControl issue stays the source of truth. Mirroring is opt-in per issue at approval time — you can also flip the project default to "always mirror".

For teams who run Jira/Linear as their primary system: enable mirror on creation so every approved FixControl issue lands in the tracker immediately. Most engineers will never need to log into FixControl directly; they work in Jira/Linear and FixControl handles intake + patch generation behind the scenes.

Reconnect & rotation

OAuth tokens refresh automatically. If a refresh fails (consent revoked, app deleted), the integration enters reconnect_required. The integrations health panel surfaces it; the admin clicks Reconnect and re-consents. No mirror data is lost — pending mirror jobs queue up while the connection is down and replay once it's restored.

FAQ

Can the same FixControl tenant mirror to both Jira and Linear? Not at once for the same issue, but you can configure different projects to use different trackers. Mirror destination is a per-project setting.

What if my Jira workflow doesn't have a "Done" status? Edit the status mapping in Settings → Integrations → Jira → Status mapping. Pick the transition closest in meaning. FixControl never invents transitions — it picks from the ones your workflow exposes.

Can I disable mirroring after it's been set up? Yes. Toggle Active off on the integration. Already-mirrored issues keep their tracker deep link; new approvals stop creating tracker issues. Re-enable any time.

Anything unclear or wrong?Let us know →

FixControl is a trade name of FixControl B.V. i.o.