> ## Documentation Index
> Fetch the complete documentation index at: https://gecko.security/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# Service accounts

> Team-owned API tokens for pipelines, internal platforms, and automation, and how to design around them.

A service account is a member of your Gecko team that has no login and owns
exactly one API token. Use one for anything that must keep working when people
change roles or leave: CI pipelines, internal developer platforms, scheduled
exports, and secret-rotation jobs.

> [!NOTE]
>
> Service accounts are available on **Pro and Enterprise** plans, and only team
> **Admins** can create them.

> [!NOTE]
>
> This page covers Gecko API tokens. The **GitLab service account** is a
> different thing: a user on your GitLab instance whose personal access token
> Gecko uses to read code. See
> [Connect self-managed GitLab](https://gecko.security/docs/connect/gitlab-self-managed.md#why-a-service-account).

## Personal keys and service account tokens

Both authenticate the [v1 API](https://gecko.security/docs/api-reference/introduction.md) and MCP the same
way. They differ in who they act as.

| | Personal API key | Service account token |
| - | - | - |
| Acts as | The person who created it | The service account, a team member with no login |
| Role | The owner's current role | Its own role: **Read Only**, **Member**, or **Manager**, never **Admin** |
| Repositories it can read | What the owner can see through their [linked Git account](https://gecko.security/docs/access/identity-model.md) | Every repository connected to the team |
| When the creator leaves the team | Stops working | Keeps working |
| Plan seat | None of its own | Uses one member seat |
| Tokens per identity | Several | Exactly one |
| Expiration | 7, 30, 90, or 365 days | 7, 30, 90, or 365 days |

Gecko resolves the role on every request. Change a service account's role in
**Settings** > **Members** and the token's access changes on its next call.

## Create a service account

**1. Open the create dialog**

Go to **Settings** > **API Keys**, click **Create API Key**, and select
**Service account token**.

**2. Name it after the workload**

Use a name that identifies the system that holds the token, such as
`gitlab-ci-prod` or `secops-export`. The name appears in the API Keys table
and on the Members page.

**3. Pick the narrowest role**

**Read Only** is the default. See [Choose a role](#choose-a-role).

**4. Set an expiration**

Choose 7, 30, 90, or 365 days. Every token expires.

**5. Store the token**

Gecko shows the token **once**. Put it straight into your secrets manager;
afterward only a masked preview is shown.

The service account then appears in **Settings** > **Members** with a
**Service** tag, and its token appears in **Settings** > **API Keys** with a
**Service account** tag.

## Choose a role

Pick the lowest role that covers every endpoint the workload calls.

| What the workload does | Endpoints | Minimum role |
| - | - | - |
| Read scans, findings, repositories, and schedules | `GET /scans`, `GET /vulnerabilities`, `GET /repositories`, `GET /schedules`, and their sub-resources | Read Only |
| Look up scanner image releases ([hybrid](https://gecko.security/docs/deployment/hybrid.md#scanner-versions-and-updates)) | `GET /releases/{channel}`, `/latest`, `/{version}` | Read Only |
| Check GitLab connection state and token expiry | `GET /integrations/gitlab` | Read Only |
| Start scans on a branch or commit | `POST /scans` | Member |
| Triage findings: dismiss, mark fixed, accept risk, reopen | `PATCH /vulnerabilities/{id}` | Member |
| Create and change scan schedules | `POST`, `PATCH`, `DELETE /schedules` | Manager |
| Change repository settings, such as `fail_on_severity` | `PATCH /repositories/…` | Manager |
| Rotate the GitLab service account token | `PATCH /integrations/gitlab` | Manager |

The full matrix is in [Teams & permissions](https://gecko.security/docs/teams-permissions.md).

## Architecture considerations

### Give each workload its own service account

Create one service account per system and environment, such as CI, your
developer platform, and your SIEM export, rather than one shared token.
Separate accounts give you:

* **A clear record.** Everything a token does is recorded against its service
  account, so the scans list and finding history show which system acted.
* **A separate rate-limit budget.** [Rate limits](https://gecko.security/docs/api-reference/introduction.md#rate-limits)
  apply per key, and a service account has one key, so a noisy export can't
  throttle your CI.
* **Smaller blast radius.** You can revoke one system's access without
  touching the others.
* **Least privilege.** A read-only export never needs the Member role your CI
  needs to start scans.

Each service account uses a member seat, so plan seats for them.

### The token reads every repository in the team

A service account isn't filtered by Git host permissions. A token can read
findings for every repository connected to the team, whatever its role.

That's what a central pipeline or export needs. It also means the token must
never reach individual developers or their tools. Don't bake it into a
developer machine image or a global environment variable, a shared CLI
config, an AI coding agent's skill or plugin, or repository CI variables that
every project maintainer can read. Anyone who can read it can list every
finding in the team.

### Let developers and agents scan through your own service

To let developers, or the coding agents they run, scan their own work without
Gecko seats or credentials, put a service you own between them and Gecko.
Only that service holds the token.

```mermaid theme={null}
flowchart LR
    D["Developer, CLI, or agent skill"] -- "developer's own credentials" --> B["Your service"]
    B -- "checks repository access" --> G["GitLab or GitHub"]
    B -- "service account token" --> A["Gecko API"]
```

Gecko scans code on your Git host, not on the developer's machine, so work in
progress has to be pushed first. A typical flow:

**1. Push the work**

The developer or agent pushes to a branch, such as a short-lived scan
branch, and calls your service with the repository and commit SHA.

**2. Authorize the request**

Your service authenticates the caller with your existing identity, such as
SSO or their Git host token, and checks on your Git host that they have the
access you require to scan that repository. Reading a repository isn't
enough on its own: every scan uses your team's quota, so require write
access, such as having pushed the commit.

**3. Reuse or start a scan**

To skip a duplicate, look in your own records for a scan your service
already started for the same Git host, repository, and commit whose
`status` is `completed` or still running. Don't search `GET /scans` for
one: its `repository` filter matches the path on every connected Git host,
and a scan doesn't say which host it came from, so a match could belong to
a same-named repository the caller can't access. Otherwise, including when
your recorded scan failed, was cancelled, or was skipped, call
`POST /scans` with `name`, `repository`, and `commit`; `commit` pins the
scan to that exact commit. For GitLab, also send `provider: "gitlab"` or
pass the full project URL as `repository`: a bare `group/project` path is
treated as GitHub.

Make the create idempotent, so a lost response doesn't cost a second scan.
Record a new `Idempotency-Key` before you call `POST /scans`, and resend
the same key and the same body on network retries. Once the first request
has finished, Gecko replays its response for 24 hours from the first
attempt. While it's still running, a retry gets `409` with
`idempotency_in_flight`: back off and retry with the same key, rather than
giving up on a scan that's still starting. Use a new key only when you
deliberately start a fresh scan, such as after a failed, cancelled, or
skipped one.

**4. Wait for it**

Poll `GET /scans/{id}` until `is_done` is `true`. If you subscribe to
[webhooks](https://gecko.security/docs/api-reference/webhooks.md), use them to check sooner, but keep a
slower poll running: an event that fails every delivery attempt is never
resent. Failed and cancelled scans are done
too, so check that `status` is `completed` before you read findings, and
treat any other status as a failure rather than a clean result.

**5. Return only that scan's findings**

Read `GET /scans/{id}/vulnerabilities`, following `next_cursor` until
`has_more` is `false`, and return those findings to the caller. Record the
scan ID with its Git host, repository, and commit, and against each caller
you served it to, and serve later reads only for scans recorded against
them.

> [!WARNING]
>
> Keep the branch until the scan finishes. The scanner fetches the commit from
> your Git host while it runs, and a commit that's no longer on any branch may
> not be fetchable. Delete a temporary branch only once `is_done` is `true`.

If the agent fixes a finding and you want that recorded in Gecko, your service
can call `PATCH /vulnerabilities/{id}` on the caller's behalf, which needs the
**Member** role. Gecko only checks the service account's role, so your service
decides who may triage: allow it only for callers with write access to the
repository, and only for findings from scans recorded against them. For
`dismiss` and `accept_risk`, the `reason` is saved as the finding's dismissal
reason; for `resolve`, it's kept as a note in the finding's activity history.

### Keep your own record of who asked

Gecko records every scan and triage action against the service account, not
the person behind your service or pipeline. Log the human or pipeline identity in your system for each call,
together with the `X-Request-Id` header Gecko returns. That lets you trace any
Gecko scan or triage decision back to whoever asked for it.

### Plan rotation before the first expiry

Every token expires, and Gecko doesn't send expiry reminders for API keys. An
expired token gets `401` with an `authentication_error`, so a forgotten expiry
shows up as a failing pipeline.

A service account has exactly one token, so you rotate by replacing the
service account:

**1. Create the replacement**

Create a new service account with the same role, and store its token in your
secrets manager.

**2. Roll it out**

Deploy the new token to the workload and confirm its calls succeed.

**3. Remove the old account**

In **Settings** > **Members**, choose **Remove from Team** on the old service
account.

During the overlap both accounts use a seat. Track each token's date in the
**Expires** column of **Settings** > **API Keys**, and schedule the rotation
well before it.

### No interactive sign-in

A service account can't sign in to the dashboard, so SSO and MFA don't apply
to it. The token is the only credential: store it in a secrets manager,
inject it at runtime, and never commit it to source control. Send it over
HTTPS as `Authorization: Bearer <token>` or `X-API-Key: <token>`.

### One team per token

A token is bound to the team it was created in. If you run several Gecko
teams, such as one per business unit, create a service account in each.

### Network egress

API calls go to `https://app.gecko.security/api/v1`. If your workloads
egress through a proxy or TLS inspection layer, allow that host.

## Reference patterns

> **CI pipeline gate**
>
> A **Member** service account per CI system. The pipeline starts a scan
> with `POST /scans` for the merge request's branch or commit, polls
> `GET /scans/{id}` until `is_done` is `true`, using
> [webhooks](https://gecko.security/docs/api-reference/webhooks.md) only to check sooner. It fails the job unless
> `status` is `completed`, then pages through
> `GET /scans/{id}/vulnerabilities` until `has_more` is `false` and fails
> the job above your severity threshold. So a retried job doesn't start a
> second scan, send an `Idempotency-Key` on `POST /scans` that stays the
> same across retries, such as one built from the pipeline ID and commit
> SHA, with the same request body. A fresh random key on each attempt
> starts a new scan. Change the key only when you deliberately rescan
> after a failed, cancelled, or skipped scan.

> **Coding agent security review**
>
> A **Member** service account held only by an internal service, as in
> [Let developers and agents scan through your own service](#let-developers-and-agents-scan-through-your-own-service).
> The agent's skill pushes its work, asks your service for a scan of that
> commit, and gets back only that scan's findings to fix before it opens a
> merge request. Scan once when the work is done rather than on every
> commit.

> **Export to a SIEM or data warehouse**
>
> A **Read Only** service account. Run a scheduled job that pages through
> `GET /vulnerabilities` with cursor pagination, or receive
> [webhook events](https://gecko.security/docs/api-reference/webhooks.md) and pull full records on
> change.

> **Automated GitLab token rotation**
>
> When your secrets manager rotates the GitLab service account's personal
> access token, a **Manager** service account calls
> `PATCH /integrations/gitlab` with the new token. Gecko validates it against
> your instance before storing it. Monitor `GET /integrations/gitlab` for the
> token's expiry date.

## Revoke access

* **Remove the service account** with **Remove from Team** in **Settings** >
  **Members**. The token stops working on its next request and the seat frees
  up.
* **Delete the token** in **Settings** > **API Keys**. The token stops working
  immediately, but the service account stays a member and keeps its seat
  until you remove it.

Removing the admin who created a service account doesn't affect it or its
token.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.