---
title: "Dashboard Push API"
description: "Push scan results from CLI or CI/CD to the hosted ComplyForm dashboard."
---

import { Tabs, TabItem, Aside } from '@astrojs/starlight/components';

## Endpoint

```
POST /v1/dashboard/push
```

**Authentication:** API key via `X-API-Key` header.
**Required tier:** Team+ (`hosted_dashboard` feature).

## CLI Usage

The CLI wraps this endpoint. After a scan, push results in one command:

```bash
complyform dashboard push --project-label=prod-gcp
```

The CLI reads scan results from the local state, serializes them, and posts to the API. You do not need to construct the JSON payload manually unless you are integrating outside the CLI.

## Request

### Headers

```
Content-Type: application/json
X-API-Key: cf_api_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
```

### Body

```json
{
  "project_label": "prod-gcp",
  "cloud": "gcp",
  "cloud_account_id": "my-org-project-123",
  "frameworks": ["cis_gcp_v2.0", "soc2"],
  "score": 78.4,
  "passed": 142,
  "failed": 39,
  "severity_counts": {
    "critical": 3,
    "high": 12,
    "medium": 18,
    "low": 6
  },
  "findings": [
    {
      "rule_id": "cis_gcp_v2.0_2.1",
      "resource": "google_sql_database_instance.production",
      "status": "FAIL",
      "severity": "high",
      "message": "Cloud SQL instance does not enforce SSL connections",
      "remediation": "Set `settings.ip_configuration.require_ssl = true`"
    },
    {
      "rule_id": "cis_gcp_v2.0_4.3",
      "resource": "google_compute_firewall.allow_all_ingress",
      "status": "FAIL",
      "severity": "critical",
      "message": "Firewall rule allows unrestricted ingress on all ports",
      "remediation": "Restrict source_ranges from 0.0.0.0/0 to specific CIDRs"
    }
  ],
  "state_source": "local",
  "scanned_at": "2026-03-23T14:30:00Z"
}
```

| Field              | Type     | Required | Description                                          |
| ------------------ | -------- | -------- | ---------------------------------------------------- |
| `project_label`    | string   | yes      | Unique label for the project within your account      |
| `cloud`            | string   | yes      | Cloud provider: `gcp`, `aws`, or `azure`              |
| `cloud_account_id` | string   | yes      | Cloud account or project identifier                   |
| `frameworks`       | string[] | yes      | List of framework IDs scanned                         |
| `score`            | number   | yes      | Overall compliance score (0-100)                      |
| `passed`           | integer  | yes      | Number of passed checks                               |
| `failed`           | integer  | yes      | Number of failed checks                               |
| `severity_counts`  | object   | yes      | Breakdown by severity: critical, high, medium, low    |
| `findings`         | object[] | yes      | Array of individual finding objects                    |
| `state_source`     | string   | yes      | Source of Terraform state: `local`, `remote`, or `ci`  |
| `scanned_at`       | string   | yes      | ISO 8601 timestamp of the scan                        |

## Response

### 200 OK

```json
{
  "project_id": "proj_a1b2c3d4e5f6",
  "scan_id": "scan_20260323_143000_prod-gcp",
  "score_delta": -2.1,
  "dashboard_url": "https://dashboard.complyform.dev/projects/proj_a1b2c3d4e5f6/scans/scan_20260323_143000_prod-gcp"
}
```

| Field           | Type   | Description                                              |
| --------------- | ------ | -------------------------------------------------------- |
| `project_id`    | string | Stable project identifier                                 |
| `scan_id`       | string | Unique scan identifier for this push                      |
| `score_delta`   | number | Change from the previous scan score (negative = regression) |
| `dashboard_url` | string | Direct link to view this scan in the dashboard            |

## Key Behaviors

### Score Delta Calculation

`score_delta` is computed as an atomic Firestore transaction. The API reads the previous scan's score and writes the new score in a single operation, preventing race conditions when multiple CI jobs push concurrently.

### Findings Storage

Individual findings are stored in Google Cloud Storage, not in Firestore. The Firestore document for each scan contains metadata (score, counts, timestamp) and a GCS reference to the full findings payload. This keeps Firestore document sizes small and query performance predictable.

### Project Matching and Creation

The API matches on `project_label` within your account:

- If a project with that label exists, the scan is appended to its history.
- If no matching project exists, one is created automatically.

<Aside type="caution">
  Project labels are immutable after creation. Choose a naming convention (e.g., `env-cloud-region`) before your first push.
</Aside>

### Project Limits

The number of active projects is capped by tier:

| Tier   | Max Projects |
| ------ | ------------ |
| Team   | 1            |
| Agency | 5            |

Pushing to a new `project_label` when you have reached your limit returns `409`.

## Error Responses

### 403 Forbidden

```json
{
  "error": "insufficient_tier",
  "message": "hosted_dashboard requires Team tier or above. Current tier: Individual."
}
```

Your license does not include the `hosted_dashboard` feature. Upgrade to Team+ to use the dashboard push API.

### 409 Conflict

```json
{
  "error": "project_limit_reached",
  "message": "Team tier allows 1 project. Archive an existing project or upgrade to Agency."
}
```

You have reached the maximum number of active projects for your tier.

### 429 Too Many Requests

```json
{
  "error": "rate_limit_exceeded",
  "message": "Dashboard write limit: 5/min. Retry after 42 seconds."
}
```

Back off and retry. In CI/CD pipelines, add a delay between parallel scan jobs that push results.

## CI/CD Integration

For automated pushes in CI/CD pipelines, see the [CI/CD guide](../../guides/cicd/) which covers GitHub Actions, GitLab CI, and other providers.

Ensure your API key is stored as a secret in your CI environment — never hardcode it in pipeline definitions. See [Authentication](../authentication/) for key storage guidance.
