# FortiGate (FortiOS REST API)

> Connect a FortiGate's management API with a read-only REST API admin: what the agents read, what the estate harvest draws, and the read gate. Drafted from Fortinet's public docs.

_How-to · Updated 2026-10-28 · Describes release 0.1.164_

> Drafted from Fortinet's public documentation, then validated against a FortiGate-VM running FortiOS 8.0.2 in October 2026: the read-only REST API admin, the estate harvest (device, interfaces, routes, address objects, policies with hit counts) and the traffic log read below. Other FortiOS versions may answer differently; [Sources](#sources) lists the public docs. When your device answers differently, the harvest notes and the agent's evidence say so.

## Overview {#overview}

A FortiGate connection reads the firewall's management API, never its data path: the **Incident** agent looks up which policy a flow meets, whether traffic really hits it (the policy hit counters), interface and route state and the recent denies in the traffic log, and the estate harvest puts the firewall, its interfaces and the traffic its policies allow on the estate map, each policy with its hit count and last use. FortiGate on AWS runs as a VM on EC2; connect the management interface, the one its REST API admins log in to.

A built-in skill, `fortigate-flow-check`, walks the agent through a flow that crosses the firewall. The agent sees it only in a workspace with a FortiGate connection.

## Setup {#setup}

### Before you start {#before-you-start}

- The `datasources.manage` permission; the built-in owner and operator roles carry it.
- A REST API admin on the FortiGate whose administrator profile is read-only ([Installation](#installation)), and its API key.
- A network path from the platform to the management URL, or a [private-network agent](/help/private-access.md) where the platform cannot reach it. The trusted host on the REST API admin must be the address the platform or the agent connects from.

### Installation {#installation}

On the FortiGate:

1. Choose a read-only administrator profile. The built-in `super_admin_readonly` gives generic read-only access. For a narrower one, create a profile and set every area the agent reads to `Read`: firewall (policy, address, service), network (interfaces, routing), system, and log & report. Set every other area to `None`. No area may be `Read/Write`, and do not use `super_admin`.
2. Go to `System > Administrators`, select `Create New > REST API Admin`, give it a name such as `aiops-reader`, assign the profile, and add a trusted host for the address the platform or your private-network agent connects from (FortiOS does not accept `0.0.0.0/0` there). A REST API admin of its own keeps the platform's calls apart from people's logins in the FortiGate's logs.
3. Select `OK` and copy the API key: FortiOS shows it once. The CLI equivalent is `config system api-user` with `set accprofile` and a `config trusthost` entry, then `execute api-user generate-key <name>`.

The platform sends the key as `Authorization: Bearer <key>`, the header FortiOS recommends. It never sends it as `access_token` in the URL, where it would land in web server and proxy logs; FortiOS accepts that only with `set rest-api-key-url-query enable` under `config system global`, which the platform does not need. The read gate below is a floor, not a substitute for a read-only profile.

### Configuration {#configuration}

1. Open **Settings · Data in → Data sources** and select **Connect datasource**.
2. Under **Type**, choose **FortiGate (FortiOS REST API)**. Enter a **Connection name**; the connected list shows it, and the agent's tool is named after it (`<name>_request`).
3. Fill the fields; the TLS-skip field sits under **Advanced connection settings**:

| Field (portal label) | API key | Value |
|---|---|---|
| Management URL (https://\<fortigate\>[:\<admin port\>], without /api/v2) | `url` | The management interface, with the admin port when it is not 443 |
| REST API admin token (read-only admin profile) | `api_key` | The REST API admin's key; stored write-only |
| VDOM (optional — blank = the REST API admin's own VDOM) | `vdom` | Set only on a multi-VDOM unit; the harvest sends it as `?vdom=` |
| Skip TLS verify for a self-signed cert (optional — type: true) | `insecure` | `true` for the factory self-signed management certificate |

4. Optionally, open **Optional · describe this environment** and say in **What should AIOps know about it?** what the firewall protects and which VDOM matters.
5. Under **Use this connection for**, choose **Investigate only** and select **Connect and verify**.

The same connection through the API (`POST /api/aiops/{tenant_id}/datasources`, requires `datasources.manage`; the key goes in `secret_values`):

```json
{
  "type_key": "fortigate",
  "name": "edge-fgt",
  "description": "Internet edge FortiGate-VM in the shared-services VPC; port1 is WAN, port2 the app subnets.",
  "config": {
    "url": "https://fortigate.example.com",
    "vdom": "root",
    "insecure": "true"
  },
  "secret_values": {
    "api_key": "<REST API admin key>"
  },
  "role": "read",
  "enabled": true
}
```

### Validation {#validation}

**Connect and verify**, and **Test** on the row later, sends a GET to `/api/v2/monitor/system/status` (the API's `get system status`) with the key as `Authorization: Bearer`. **You should see** a dialog titled `<connection name> is connected` with **Connected — verified from the control plane**, and later in the row's **Last test** `ok · HTTP 200 /api/v2/monitor/system/status — {"http_method":"GET","results":{…`. A `401` means the key is wrong or the platform's address is not a trusted host; a `403` means the profile does not read system.

### If something fails {#if-something-fails}

The dialog shows **Needs a fix**, what to fix and the check's own detail; the row is saved either way. Select **Edit**, correct the field the detail names and save again, or select **Retry** once a cause outside the form (network, trusted host, profile) is fixed. Every verdict this connector produces is in [Troubleshooting](#troubleshooting) below.

## Data collected {#data-collected}

### Tools the agent gets {#tools}

- `<name>_traffic_log` — one page of the traffic log, read-only: `source` (`memory`, the default, or `disk`), `subtype` (`forward`, the default, `local`, `multicast` or `sniffer`), up to four `filters` ANDed (`field<op>value` with `==`, `!=`, `=@`, `!@`, `<=`, `>=`, `<` or `>`, for example `dstip==10.0.1.5`), `rows` (at most 100) and `start` (the answer's `next_start` reads the next page). A search the firewall is still running is read again with its session, at most three times. Each row keeps the flow fields (time, action, policy, interfaces, addresses, ports, service, application, bytes, duration); who sent it (user, group, device name, MAC address, OS) is never returned, and the rest is scrubbed like every tool output, with your workspace's own PII settings. It sends `GET /api/v2/log/<source>/traffic/<subtype>`: the REST API admin's profile needs read access to the logs. That request (memory, forward, `rows`, a `srcip==` filter, the VDOM) answered on FortiOS 8.0.2; the tool's paging and its re-read of a search still running have not run on a device yet.
- `<name>_request` — the HTTP tool against the management URL with the key: `method`, `path`, `query`, `body`, `content_type`. The skill uses `GET /api/v2/monitor/firewall/policy-lookup` (which policy a flow meets), `GET /api/v2/cmdb/firewall/policy/<id>`, `GET /api/v2/monitor/firewall/policy` with `policyid` (that policy's hit counters: `hit_count`, `bytes`, `first_used`, `last_used` in epoch seconds), `GET /api/v2/cmdb/firewall/address/<name>`, `GET /api/v2/monitor/router/lookup` and `/api/v2/monitor/router/ipv4`, `GET /api/v2/monitor/system/interface` and `GET /api/v2/log/memory/traffic/forward` with a `filter` (`disk` in place of `memory` on a unit that logs to disk).

Responses are capped at 20,000 characters per call.

### Read surface {#read-surface}

Only `GET` reads. `POST`, `PUT`, `DELETE` and even `HEAD` are refused in an investigation, never offered for approval: `Investigation connections are read-only. This operation was not executed; prepare an incident change using a write connection and obtain approval.` Some `GET`s return secrets and are refused too: the configuration backup (`/api/v2/monitor/system/config/backup`) and the tables that hold credentials — `system/admin`, `system/api-user`, `user/local`, `vpn.ipsec/phase1` and `phase1-interface`, `vpn.certificate/local`. A request that carries `access_token` in its query is refused as well: the key belongs in the header. Clearing the policy counters is a `POST` and so refused. The full rule table is under **The read/write gate in detail** in [Connections and remediation access](/help/read-and-write-roles.md#the-read-write-gate-in-detail).

### Estate harvest {#estate-harvest}

**Harvest now** on the row, the daily inventory job and an estate x-ray scan read six GETs: `monitor/system/status`, `cmdb/system/interface`, `cmdb/firewall/address`, `monitor/firewall/policy` (the policy hit counters), `cmdb/firewall/policy` and `monitor/router/ipv4`. They become:

- one **Firewall** node named after the hostname, carrying the vendor, model, FortiOS version and build, serial and the counts of policies (allow, deny, disabled), interfaces, address objects and routes;
- one interface node per interface, with its address, subnet (`cidr`), status, role and the route destinations that leave through it;
- one policy node per enabled `accept` policy, linked to its destination interfaces (applies to) and source interfaces (allows from), with its sources and destinations summarised as `name=value` from the address objects, its services, and `world_open` when the source is `all`. The rules then derive that the destination interface is reachable from the source one, which is the traffic the firewall lets through. Each policy node also carries its usage: `hit_count` and `bytes` (the IPv4 and IPv6 counters added up), `first_used` and `last_used` in UTC. `last_used` reads `never` for a policy with no hit since its counters were last cleared: an allow rule nothing uses, worth a rule review. When the profile cannot read the counters, the harvest notes the refusal and the policies carry no usage at all, which means unknown, not unused. The counters move with traffic, so the scan report's estate changes never list them as a change;
- one policy node per enabled `deny` policy, with its `action`, its sources, destinations and services and the same usage, but no link to an interface and no `world_open`: a deny never reads as traffic the firewall lets through. An `ipsec` policy, which sends traffic into a VPN tunnel, is drawn the same way, without interface links.

Deny policies are drawn with their action and usage and no interface links; disabled policies are counted, not drawn. Interfaces and policies are leaf detail: on the [Estate map](/portal/estate) (in the product: `/portal/estate`) they show when you tick *Show every object, rules and interfaces included* or focus the firewall. The firewall counts for the map's firewall check and the x-ray report's **Firewall** line, and an enabled FortiGate connection counts on its own before the first harvest. A denied or failed read is a note on the harvest, never a crash, and what it would have listed is not marked stale. Not read yet: zones (a policy that names a zone keeps the name in its props), address groups and VIPs. A FortiGate VM the AWS harvest also lists as an instance is joined to it by address (an interface's address is the private address of the instance or of one of its network interfaces): it counts as one firewall, and its row in a [service group](/help/estate-harvest-and-xray.md#service-group) carries the enabled policies and the connection that read them. An address two instances or two firewalls hold joins nothing.

## Troubleshooting {#troubleshooting}

| What you see | Cause | What to do |
|---|---|---|
| `failed · HTTP 401 /api/v2/monitor/system/status` | The key is wrong, or the platform's address is not a trusted host of the REST API admin. | Check the trusted host first, then regenerate the key and replace it on **Edit**. |
| `failed · HTTP 403 /api/v2/monitor/system/status` | The administrator profile does not read system. | Set system to `Read` in the profile. |
| `failed · HTTP 404 /api/v2/monitor/system/status` | The URL already ends in `/api/v2`, or points at a port that is not the admin port. | Enter the management root only, with the admin port. |
| `failed · ConnectError: …` or a certificate error | Wrong port, a management interface the platform cannot reach, or the self-signed certificate. | Use the admin port, a [private-network agent](/help/private-access.md), or `insecure: true`. |
| The harvest notes `HTTP 403` on one listing (`fortigate GET /api/v2/cmdb/firewall/policy: …`) | The profile does not read that area. | Set the area to `Read`; the next harvest fills it in. |
| A `POST`, `PUT` or a config backup answers `Investigation connections are read-only.` | The write gate. | Expected. |

The generic verdict strings are explained in [Test a connection and read the failure](/help/manage-connections.md#troubleshooting). Every connection status word is in the [status and error reference](/help/status-and-error-reference.md#connection-statuses).

## Next steps {#next-steps}

- Choose **Harvest now** on the connection's row in [Data sources](/portal/datasources) (in the product: `/portal/datasources`) to draw the firewall on the map: [Estate harvest](/help/estate-harvest-and-xray.md#estate-harvest).
- Select **Discover this connection** in the success dialog to find the services behind the firewall: [Launch a scan](/help/estate-harvest-and-xray.md#launch-a-scan).
- Give approved firewall changes their own credential: [Write connections and approved fixes](/help/setup-write-connections.md).

## Sources {#sources}

- REST API administrator (least privilege, `super_admin_readonly`, trusted hosts, the key shown once, `config system api-user`): https://docs.fortinet.com/document/fortigate/7.4.6/administration-guide/399023/rest-api-administrator
- The key as `Authorization: Bearer`, recommended over the URL query and its `rest-api-key-url-query` setting: https://docs.fortinet.com/document/fortigate/7.6.5/administration-guide/940602
- The key as `Authorization: Bearer` and the `monitor/system/status` answer: https://community.fortinet.com/fortigate-3/technical-tip-python-script-example-for-fortigate-rest-api-211989
- The policy hit counters (`monitor/firewall/policy`) read by a separate REST API admin: https://community.fortinet.com/fortigate-3/technical-tip-testing-the-fortigate-rest-api-to-pull-firewall-policy-hit-count-and-other-cmdb-monitor-data-using-a-windows-batch-script-230279; their fields and epoch-second times as a device printed them: https://community.fortinet.com/support-forum-92/fortigate-api-date-format-82
- Policy, interface and address fields: https://ansible-galaxy-fortios-docs.readthedocs.io/en/latest/gen/fortios_firewall_policy.html, https://ansible-galaxy-fortios-docs.readthedocs.io/en/latest/gen/fortios_system_interface.html, https://ansible-galaxy-fortios-docs.readthedocs.io/en/latest/gen/fortios_firewall_address.html
- The routing table monitor (third-party client reference; the official FortiOS API reference needs a Fortinet Developer Network login): https://hfortix-fortios.readthedocs.io/en/latest/endpoints/monitor/router/ipv4.html
