
Grafana
- 47 installs
- 6 repo stars
- Updated July 22, 2026
- julianobarbosa/claude-code-skills
Helps with ai & agent building tasks.
About
grafana is a Claude Code skill for ai & agent building. It helps solo builders move faster with AI-assisted development.
- grafana
- AI & Agent Building
- AI-coding skill
Grafana by the numbers
- 47 all-time installs (skills.sh)
- +1 installs in the week ending Aug 2, 2026 (Skillselion tracking)
- Ranked #7,551 of 16,546 AI & Agent Building skills by installs in the Skillselion catalog
- Data as of Aug 3, 2026 (Skillselion catalog sync)
npx skills add https://github.com/julianobarbosa/claude-code-skills --skill grafanaAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 47 |
|---|---|
| repo stars | ★ 6 |
| Last updated | July 22, 2026 |
| Repository | julianobarbosa/claude-code-skills ↗ |
What it does
Helps with ai & agent building tasks.
Files
grafana-skill
Programmatically manage Grafana resources using TypeScript tools and HTTP API workflows.
Workflow Routing
| Workflow | Trigger | File |
|---|---|---|
| DashboardCrud | "create dashboard", "update dashboard", "delete dashboard", "list dashboards", "export dashboard" | Tools/DashboardCrud.ts |
| GrafanaClient | "grafana API", "grafana client", "TypeScript grafana" | Tools/GrafanaClient.ts |
| ApiReference | "grafana API reference", "grafana endpoints" | References/ |
Tools
DashboardCrud CLI
# Set environment variables
export GRAFANA_URL="https://grafana.example.com"
export GRAFANA_TOKEN="your-service-account-token"
# List dashboards
bun run Tools/DashboardCrud.ts list
bun run Tools/DashboardCrud.ts list --query production --tag monitoring
# Get dashboard by UID
bun run Tools/DashboardCrud.ts get abc123
# Export dashboard to JSON
bun run Tools/DashboardCrud.ts export abc123 --output dashboard.json
# Create dashboard from JSON file
bun run Tools/DashboardCrud.ts create --file dashboard.json --folder my-folder
# Update dashboard
bun run Tools/DashboardCrud.ts update abc123 --file updated.json --message "Updated panels"
# Clone dashboard
bun run Tools/DashboardCrud.ts clone abc123 --title "Production Copy" --folder prod-folder
# View version history
bun run Tools/DashboardCrud.ts versions abc123
# Restore to previous version
bun run Tools/DashboardCrud.ts restore abc123 --version 5
# Delete dashboard
bun run Tools/DashboardCrud.ts delete abc123GrafanaClient TypeScript Library
import { GrafanaClient, createGrafanaClient } from './Tools/GrafanaClient';
// Initialize from environment variables
const client = createGrafanaClient();
// Or with explicit config
const client = new GrafanaClient({
baseUrl: 'https://grafana.example.com',
token: 'your-service-account-token',
orgId: 1, // optional
});
// Dashboard operations
const dashboards = await client.searchDashboards({ query: 'production', tag: 'monitoring' });
const dashboard = await client.getDashboardByUid('abc123');
const saved = await client.saveDashboard({ dashboard: myDashboard, folderUid: 'folder-uid' });
await client.deleteDashboard('abc123');
// Version management
const versions = await client.getDashboardVersions('abc123');
await client.restoreDashboardVersion('abc123', 5);
// Folders, Data sources, Alerting, Annotations also availableAuthentication
# Service Account Token (Recommended)
export GRAFANA_TOKEN="glsa_xxxxxxxxxxxxxxxxxxxx"
# Multi-Organization Header
curl -H "Authorization: Bearer $GRAFANA_TOKEN" \
-H "X-Grafana-Org-Id: 2" \
https://grafana.example.com/api/orgQuick API Reference
| Resource | List | Get | Create | Update | Delete |
|---|---|---|---|---|---|
| Dashboards | GET /api/search | GET /api/dashboards/uid/:uid | POST /api/dashboards/db | POST /api/dashboards/db | DELETE /api/dashboards/uid/:uid |
| Folders | GET /api/folders | GET /api/folders/:uid | POST /api/folders | PUT /api/folders/:uid | DELETE /api/folders/:uid |
| Data Sources | GET /api/datasources | GET /api/datasources/uid/:uid | POST /api/datasources | PUT /api/datasources/uid/:uid | DELETE /api/datasources/uid/:uid |
| Alert Rules | GET /api/v1/provisioning/alert-rules | GET /api/v1/provisioning/alert-rules/:uid | POST /api/v1/provisioning/alert-rules | PUT /api/v1/provisioning/alert-rules/:uid | DELETE /api/v1/provisioning/alert-rules/:uid |
Reference Documentation
- [Dashboards](References/Dashboards.md): Complete dashboard CRUD, versions, permissions
- [DataSources](References/DataSources.md): Data source management, queries, health checks
- [Alerting](References/Alerting.md): Alert rules, contact points, notification policies
- [Folders](References/Folders.md): Folder management and permissions
- [Annotations](References/Annotations.md): Create, query, update annotations
- [UsersTeams](References/UsersTeams.md): User management, team operations
- [CommonPatterns](References/CommonPatterns.md): Error handling, pagination, utilities
Examples
Example 1: List and export dashboards
User: "List all production dashboards and export them"
→ bun run Tools/DashboardCrud.ts list --tag production
→ For each: bun run Tools/DashboardCrud.ts export <uid>
→ Returns list of exported JSON filesExample 2: Create dashboard from JSON
User: "Create a new dashboard from this JSON file"
→ bun run Tools/DashboardCrud.ts create --file dashboard.json --folder monitoring
→ Returns new dashboard UID and URLExample 3: Clone dashboard to another folder
User: "Clone the CPU dashboard to the production folder"
→ bun run Tools/DashboardCrud.ts clone cpu-uid --title "CPU Prod" --folder prod-folder
→ Returns cloned dashboard detailsExample 4: Restore dashboard version
User: "Restore dashboard abc123 to version 5"
→ bun run Tools/DashboardCrud.ts versions abc123
→ bun run Tools/DashboardCrud.ts restore abc123 --version 5
→ Dashboard restored, new version createdExample 5: Programmatic bulk update
User: "Write TypeScript to bulk update dashboard tags"
→ Uses GrafanaClient library:
const client = createGrafanaClient();
const dashboards = await client.searchDashboards({ tag: 'old-tag' });
for (const dash of dashboards) {
const full = await client.getDashboardByUid(dash.uid);
full.dashboard.tags = full.dashboard.tags.filter(t => t !== 'old-tag');
full.dashboard.tags.push('new-tag');
await client.saveDashboard({ dashboard: full.dashboard, message: 'Updated tags' });
}Error Handling
| Code | Description |
|---|---|
| 200 | Success |
| 400 | Bad request (invalid JSON, missing required fields) |
| 401 | Unauthorized (invalid/missing token) |
| 403 | Forbidden (insufficient permissions) |
| 404 | Not found |
| 409 | Conflict (resource already exists) |
| 412 | Precondition failed (version mismatch) |
Tips
1. Use UIDs over IDs: UIDs are portable across Grafana instances 2. Include version for updates: Prevents overwriting concurrent changes 3. Use `overwrite: true` carefully: Only when you want to force-update 4. Service accounts over API keys: API keys are deprecated in newer Grafana versions
---
Gotchas
- Datasource permission: viewers can see the panel but get "query forbidden" if the datasource has explicit per-team permissions — confusing for non-admin users.
- Variable interpolation:
$__intervaland$__nameare interpreted differently in legacy vs newer query backends; same dashboard JSON behaves differently. - Provisioning datasources via YAML under
/etc/grafana/provisioning: edits via UI are silently reverted on file re-read; pick one source of truth. - API tokens are per-org — multi-org Grafana setups need separate tokens; a global "admin" token doesn't span orgs.
- Annotation queries with missing time-field return zero annotations without error — debug by checking the raw query response, not the panel.
- Alert rule state lives in the Grafana DB, not in Prometheus — rolling Grafana back loses alert state history.
Alerting API Reference
Complete reference for Grafana Alerting HTTP API endpoints (Grafana 9.0+).
Table of Contents
---
Alert Rules
List All Alert Rules
GET /api/v1/provisioning/alert-rulesExample Response:
[
{
"id": 1,
"uid": "cIBgcSjkk",
"orgID": 1,
"folderUID": "l3KqBxCMz",
"ruleGroup": "CPU Alerts",
"title": "High CPU Alert",
"condition": "B",
"data": [...],
"updated": "2024-06-20T14:22:00Z",
"noDataState": "OK",
"execErrState": "OK",
"for": "5m",
"annotations": {"summary": "CPU usage is high"},
"labels": {"severity": "warning"},
"provenance": ""
}
]Get Alert Rule by UID
GET /api/v1/provisioning/alert-rules/:uidCreate Alert Rule
POST /api/v1/provisioning/alert-rulesComplete Example:
{
"title": "High Memory Usage",
"ruleGroup": "Memory Alerts",
"folderUID": "l3KqBxCMz",
"noDataState": "NoData",
"execErrState": "Error",
"for": "5m",
"orgId": 1,
"condition": "C",
"annotations": {
"summary": "Memory usage above 90%",
"description": "Host {{ $labels.instance }} memory usage is {{ $values.A }}%",
"runbook_url": "https://wiki.example.com/runbooks/memory"
},
"labels": {
"severity": "critical",
"team": "platform"
},
"data": [
{
"refId": "A",
"queryType": "",
"relativeTimeRange": {"from": 600, "to": 0},
"datasourceUid": "prometheus-uid",
"model": {
"expr": "(1 - (node_memory_MemAvailable_bytes / node_memory_MemTotal_bytes)) * 100",
"instant": false,
"intervalMs": 1000,
"maxDataPoints": 43200,
"refId": "A"
}
},
{
"refId": "B",
"queryType": "",
"relativeTimeRange": {"from": 600, "to": 0},
"datasourceUid": "-100",
"model": {
"type": "reduce",
"expression": "A",
"reducer": "last",
"refId": "B"
}
},
{
"refId": "C",
"queryType": "",
"relativeTimeRange": {"from": 0, "to": 0},
"datasourceUid": "-100",
"model": {
"type": "threshold",
"expression": "B",
"refId": "C",
"conditions": [
{
"evaluator": {"type": "gt", "params": [90]},
"operator": {"type": "and"},
"query": {"params": ["C"]},
"reducer": {"type": "last"}
}
]
}
}
]
}Update Alert Rule
PUT /api/v1/provisioning/alert-rules/:uidDelete Alert Rule
DELETE /api/v1/provisioning/alert-rules/:uid---
Rule Groups
Get Rule Group
GET /api/v1/provisioning/folder/:folderUid/rule-groups/:groupUpdate Rule Group
PUT /api/v1/provisioning/folder/:folderUid/rule-groups/:groupRequest Body:
{
"name": "CPU Alerts",
"interval": "1m",
"rules": [...]
}---
Contact Points
List Contact Points
GET /api/v1/provisioning/contact-pointsQuery Parameters:
| Parameter | Type | Description |
|---|---|---|
| name | string | Filter by name |
Example Response:
[
{
"uid": "email-receiver",
"name": "email-receiver",
"type": "email",
"settings": {
"addresses": "alerts@example.com",
"singleEmail": false
},
"disableResolveMessage": false
}
]Create Contact Point
POST /api/v1/provisioning/contact-pointsEmail Example:
{
"name": "ops-team-email",
"type": "email",
"settings": {
"addresses": "ops@example.com;oncall@example.com",
"singleEmail": true
},
"disableResolveMessage": false
}Slack Example:
{
"name": "slack-alerts",
"type": "slack",
"settings": {
"url": "https://hooks.slack.com/services/T00000000/B00000000/XXXXXXXX",
"recipient": "#alerts",
"username": "Grafana",
"icon_emoji": ":grafana:",
"mentionUsers": "U12345678",
"mentionGroups": "S12345678",
"mentionChannel": "here"
}
}PagerDuty Example:
{
"name": "pagerduty",
"type": "pagerduty",
"settings": {
"integrationKey": "your-integration-key",
"severity": "critical",
"class": "ping failure",
"component": "Grafana",
"group": "Production"
}
}Webhook Example:
{
"name": "custom-webhook",
"type": "webhook",
"settings": {
"url": "https://your-endpoint.com/alerts",
"httpMethod": "POST",
"username": "grafana",
"password": "secret",
"maxAlerts": 10
}
}Update Contact Point
PUT /api/v1/provisioning/contact-points/:uidDelete Contact Point
DELETE /api/v1/provisioning/contact-points/:uid---
Notification Policies
Get Notification Policy Tree
GET /api/v1/provisioning/policiesExample Response:
{
"receiver": "email-receiver",
"group_by": ["grafana_folder", "alertname"],
"routes": [
{
"receiver": "slack-alerts",
"object_matchers": [["severity", "=", "critical"]],
"continue": false,
"group_wait": "30s",
"group_interval": "5m",
"repeat_interval": "4h"
}
],
"group_wait": "30s",
"group_interval": "5m",
"repeat_interval": "4h"
}Update Notification Policy Tree
PUT /api/v1/provisioning/policiesRequest Body:
{
"receiver": "email-receiver",
"group_by": ["grafana_folder", "alertname"],
"routes": [
{
"receiver": "pagerduty",
"object_matchers": [
["severity", "=", "critical"],
["team", "=", "platform"]
],
"continue": false,
"group_wait": "10s",
"group_interval": "1m",
"repeat_interval": "1h"
},
{
"receiver": "slack-alerts",
"object_matchers": [["severity", "=", "warning"]],
"continue": true,
"mute_time_intervals": ["weekends"]
}
],
"group_wait": "30s",
"group_interval": "5m",
"repeat_interval": "4h"
}---
Mute Timings
List Mute Timings
GET /api/v1/provisioning/mute-timingsCreate Mute Timing
POST /api/v1/provisioning/mute-timingsWeekend Mute Example:
{
"name": "weekends",
"time_intervals": [
{
"weekdays": ["saturday", "sunday"]
}
]
}Business Hours Only Example:
{
"name": "outside-business-hours",
"time_intervals": [
{
"weekdays": ["monday:friday"],
"times": [
{"start_time": "00:00", "end_time": "09:00"},
{"start_time": "17:00", "end_time": "24:00"}
]
},
{
"weekdays": ["saturday", "sunday"]
}
]
}Update Mute Timing
PUT /api/v1/provisioning/mute-timings/:nameDelete Mute Timing
DELETE /api/v1/provisioning/mute-timings/:name---
Silences
List Silences
GET /api/alertmanager/grafana/api/v2/silencesCreate Silence
POST /api/alertmanager/grafana/api/v2/silencesRequest Body:
{
"matchers": [
{"name": "alertname", "value": "HighCPU", "isRegex": false, "isEqual": true},
{"name": "instance", "value": "server-01", "isRegex": false, "isEqual": true}
],
"startsAt": "2024-06-20T10:00:00Z",
"endsAt": "2024-06-20T18:00:00Z",
"createdBy": "admin",
"comment": "Scheduled maintenance window"
}Delete Silence
DELETE /api/alertmanager/grafana/api/v2/silence/:silenceId---
Active Alerts
Get All Active Alerts
GET /api/alertmanager/grafana/api/v2/alertsQuery Parameters:
| Parameter | Type | Description |
|---|---|---|
| active | boolean | Show active alerts |
| silenced | boolean | Show silenced alerts |
| inhibited | boolean | Show inhibited alerts |
| filter | string | Filter by label matchers |
| receiver | string | Filter by receiver |
Get Alert Groups
GET /api/alertmanager/grafana/api/v2/alerts/groups---
Templates
List Templates
GET /api/v1/provisioning/templatesCreate/Update Template
PUT /api/v1/provisioning/templates/:nameRequest Body:
{
"template": "{{ define \"custom_email.subject\" }}\n[{{ .Status | toUpper }}{{ if eq .Status \"firing\" }}:{{ .Alerts.Firing | len }}{{ end }}] {{ .GroupLabels.alertname }}\n{{ end }}"
}Delete Template
DELETE /api/v1/provisioning/templates/:name---
Expression Types
For alert rule data queries, use datasourceUid: "-100" for expression types:
| Type | Description |
|---|---|
reduce | Aggregate time series (last, mean, min, max, sum, count) |
threshold | Compare against threshold values |
classic_conditions | Legacy condition format |
math | Mathematical operations on results |
resample | Resample time series data |
Reduce Example:
{
"refId": "B",
"datasourceUid": "-100",
"model": {
"type": "reduce",
"expression": "A",
"reducer": "last",
"refId": "B"
}
}Threshold Example:
{
"refId": "C",
"datasourceUid": "-100",
"model": {
"type": "threshold",
"expression": "B",
"conditions": [
{"evaluator": {"type": "gt", "params": [80]}}
]
}
}Annotations API Reference
Complete reference for Grafana Annotations HTTP API endpoints.
Table of Contents
- Query Annotations
- Create Annotation
- Create Graphite Annotation
- Update Annotation
- Patch Annotation
- Delete Annotation
- Find Annotation by ID
- Annotation Tags
---
Query Annotations
GET /api/annotationsQuery Parameters:
| Parameter | Type | Description |
|---|---|---|
| from | integer | Start time in epoch milliseconds |
| to | integer | End time in epoch milliseconds |
| limit | integer | Max annotations to return (default: 100) |
| alertId | integer | Filter by alert ID |
| dashboardId | integer | Filter by dashboard ID |
| dashboardUID | string | Filter by dashboard UID (recommended) |
| panelId | integer | Filter by panel ID |
| userId | integer | Filter by user who created annotation |
| type | string | alert or annotation |
| tags | string | Filter by tags (repeat for multiple: tags=tag1&tags=tag2) |
| matchAny | boolean | Match any tag (default: false = match all) |
Example Request:
curl -H "Authorization: Bearer <TOKEN>" \
"https://grafana.example.com/api/annotations?from=1506676478816&to=1507281278816&tags=deploy&tags=production&limit=100"Example Response:
[
{
"id": 1124,
"alertId": 0,
"dashboardId": 163,
"dashboardUID": "cIBgcSjkk",
"panelId": 2,
"userId": 1,
"userName": "admin",
"newState": "",
"prevState": "",
"time": 1507266395000,
"timeEnd": 1507266395000,
"text": "Deployment completed",
"tags": ["deploy", "production"],
"data": {}
}
]Annotation Types:
- Dashboard annotation: Associated with a specific dashboard/panel
- Organization annotation: Global annotation visible across all dashboards
- Alert annotation: Auto-created when alert state changes
---
Create Annotation
POST /api/annotationsDashboard Annotation
{
"dashboardUID": "cIBgcSjkk",
"panelId": 2,
"time": 1507037197339,
"timeEnd": 1507180805056,
"tags": ["deploy", "production"],
"text": "Deployment v2.1.0 completed"
}Organization Annotation (Global)
Omit dashboardUID and panelId to create a global annotation:
{
"time": 1507037197339,
"tags": ["maintenance", "infrastructure"],
"text": "Scheduled maintenance window started"
}Point Annotation (Single Moment)
{
"dashboardUID": "cIBgcSjkk",
"time": 1507037197339,
"tags": ["incident"],
"text": "Service outage detected"
}Region Annotation (Time Range)
Include timeEnd to create a region:
{
"dashboardUID": "cIBgcSjkk",
"time": 1507037197339,
"timeEnd": 1507040797339,
"tags": ["maintenance"],
"text": "Maintenance window"
}Request Fields:
| Field | Type | Required | Description |
|---|---|---|---|
| text | string | Yes | Annotation text/description |
| time | integer | Yes | Start time in epoch milliseconds |
| timeEnd | integer | No | End time for region annotations |
| dashboardUID | string | No | Dashboard UID (omit for org annotation) |
| dashboardId | integer | No | Dashboard ID (deprecated, use UID) |
| panelId | integer | No | Panel ID |
| tags | array | No | Array of tag strings |
| data | object | No | Custom JSON data |
Example Response:
{
"message": "Annotation added",
"id": 1125
}---
Create Graphite Annotation
POST /api/annotations/graphiteCompatible with Graphite event format:
{
"what": "Event - deploy",
"tags": ["deploy", "production"],
"when": 1467844481,
"data": "deploy of main branch happened at Wed Jul 6 22:34:41 UTC 2016"
}Fields:
| Field | Type | Required | Description |
|---|---|---|---|
| what | string | Yes | Event description |
| tags | array/string | No | Tags (array or space-separated string) |
| when | integer | No | Time in epoch seconds (default: now) |
| data | string | No | Additional event data |
---
Update Annotation
PUT /api/annotations/:annotationIdReplaces all properties of the annotation:
{
"time": 1507037197339,
"timeEnd": 1507040797339,
"text": "Updated annotation text",
"tags": ["updated", "deploy"]
}---
Patch Annotation
PATCH /api/annotations/:annotationIdUpdates only specified properties:
{
"text": "Partially updated text"
}Example - Update only tags:
{
"tags": ["new-tag", "updated"]
}Example - Extend time range:
{
"timeEnd": 1507050797339
}---
Delete Annotation
DELETE /api/annotations/:annotationIdExample Request:
curl -X DELETE -H "Authorization: Bearer <TOKEN>" \
"https://grafana.example.com/api/annotations/1124"Example Response:
{
"message": "Annotation deleted"
}---
Find Annotation by ID
GET /api/annotations/:annotationIdExample Response:
{
"id": 1124,
"alertId": 0,
"dashboardId": 163,
"dashboardUID": "cIBgcSjkk",
"panelId": 2,
"userId": 1,
"userName": "admin",
"newState": "",
"prevState": "",
"time": 1507266395000,
"timeEnd": 1507270000000,
"text": "Deployment completed",
"tags": ["deploy", "production"],
"data": {}
}---
Annotation Tags
Get Annotation Tags
GET /api/annotations/tagsReturns all unique tags used in annotations.
Query Parameters:
| Parameter | Type | Description |
|---|---|---|
| limit | integer | Max tags to return (default: 100) |
| tag | string | Filter tags containing this string |
Example Response:
{
"result": {
"tags": [
{"tag": "deploy", "count": 15},
{"tag": "production", "count": 12},
{"tag": "incident", "count": 5},
{"tag": "maintenance", "count": 8}
]
}
}---
Common Patterns
Deploy Marker
import time
import requests
def create_deploy_annotation(grafana_url, token, dashboard_uid, version, env):
response = requests.post(
f"{grafana_url}/api/annotations",
headers={
"Authorization": f"Bearer {token}",
"Content-Type": "application/json"
},
json={
"dashboardUID": dashboard_uid,
"time": int(time.time() * 1000),
"tags": ["deploy", env, f"v{version}"],
"text": f"Deployed version {version} to {env}"
}
)
return response.json()Maintenance Window
def create_maintenance_window(grafana_url, token, start_ms, end_ms, description):
response = requests.post(
f"{grafana_url}/api/annotations",
headers={
"Authorization": f"Bearer {token}",
"Content-Type": "application/json"
},
json={
"time": start_ms,
"timeEnd": end_ms,
"tags": ["maintenance", "scheduled"],
"text": description
}
)
return response.json()Clean Up Old Annotations
def delete_old_annotations(grafana_url, token, older_than_ms, tags=None):
params = {
"from": 0,
"to": older_than_ms,
"limit": 1000
}
if tags:
params["tags"] = tags
response = requests.get(
f"{grafana_url}/api/annotations",
headers={"Authorization": f"Bearer {token}"},
params=params
)
annotations = response.json()
for ann in annotations:
requests.delete(
f"{grafana_url}/api/annotations/{ann['id']}",
headers={"Authorization": f"Bearer {token}"}
)
return len(annotations)Common Patterns Reference
Error handling, pagination, and reusable patterns for the Grafana HTTP API.
Table of Contents
---
Error Handling
HTTP Status Codes
| Code | Meaning | Common Causes |
|---|---|---|
| 200 | Success | Request completed successfully |
| 201 | Created | Resource created successfully |
| 400 | Bad Request | Invalid JSON, missing required fields |
| 401 | Unauthorized | Invalid or missing authentication |
| 403 | Forbidden | Insufficient permissions |
| 404 | Not Found | Resource doesn't exist |
| 409 | Conflict | Resource already exists |
| 412 | Precondition Failed | Version mismatch (optimistic locking) |
| 422 | Unprocessable Entity | Validation error |
| 500 | Internal Server Error | Server-side error |
Error Response Format
{
"message": "Dashboard not found",
"status": "not-found"
}Or with more detail:
{
"message": "Failed to save dashboard",
"status": "validation-failed",
"messageId": "dashboards.dashboardNotFound",
"traceID": "abc123def456"
}---
Pagination
List Endpoints
Most list endpoints support pagination via limit and page:
GET /api/search?limit=50&page=1
GET /api/users/search?perpage=100&page=2Response Metadata
Some endpoints return pagination metadata:
{
"totalCount": 250,
"page": 1,
"perPage": 100,
"users": [...]
}Iterating All Pages
def get_all_pages(grafana, endpoint, key='results', page_size=100):
all_results = []
page = 1
while True:
response = grafana.get(f"{endpoint}?perpage={page_size}&page={page}")
results = response.get(key, response)
if not results:
break
all_results.extend(results)
total = response.get('totalCount', len(results))
if len(all_results) >= total:
break
page += 1
return all_results---
Rate Limiting
Grafana doesn't have built-in API rate limiting by default, but:
1. Reverse proxies may impose limits 2. Grafana Cloud has rate limits per tier 3. Best practice: Add delays between bulk operations
import time
def bulk_create_with_rate_limit(items, create_func, delay=0.1):
results = []
for item in items:
result = create_func(item)
results.append(result)
time.sleep(delay)
return results---
Common Errors
Dashboard Version Conflict (412)
Error:
{
"message": "The dashboard has been changed by someone else",
"status": "version-mismatch"
}Solution: Fetch latest version and retry:
def update_dashboard_safely(grafana, uid, updates):
dashboard_data = grafana.get_dashboard_by_uid(uid)
dashboard = dashboard_data['dashboard']
# Apply updates
dashboard.update(updates)
# Include version for optimistic locking
return grafana.create_or_update_dashboard({
'dashboard': dashboard,
'folderUid': dashboard_data['meta'].get('folderUid'),
'overwrite': False
})Permission Denied (403)
Error:
{
"message": "Access denied"
}Common causes:
- Token lacks required permissions
- User not in correct organization
- RBAC restrictions (Enterprise)
Debug: Check service account permissions in Grafana UI.
Resource Not Found (404)
Error:
{
"message": "Dashboard not found",
"status": "not-found"
}Common causes:
- Wrong UID or ID
- Resource in different organization
- Resource was deleted
---
Python Client Examples
Base Client Class
import requests
from typing import Optional, Dict, Any, List
from urllib.parse import urljoin
class GrafanaAPIError(Exception):
def __init__(self, message: str, status_code: int, response: Dict):
self.message = message
self.status_code = status_code
self.response = response
super().__init__(f"{status_code}: {message}")
class GrafanaClient:
def __init__(self, base_url: str, token: str, org_id: Optional[int] = None):
self.base_url = base_url.rstrip('/')
self.session = requests.Session()
self.session.headers.update({
'Authorization': f'Bearer {token}',
'Content-Type': 'application/json',
'Accept': 'application/json'
})
if org_id:
self.session.headers['X-Grafana-Org-Id'] = str(org_id)
def _request(self, method: str, endpoint: str, **kwargs) -> Dict:
url = urljoin(self.base_url, endpoint)
response = self.session.request(method, url, **kwargs)
try:
data = response.json()
except:
data = {'message': response.text}
if not response.ok:
raise GrafanaAPIError(
data.get('message', 'Unknown error'),
response.status_code,
data
)
return data
def get(self, endpoint: str, params: Dict = None) -> Dict:
return self._request('GET', endpoint, params=params)
def post(self, endpoint: str, json: Dict = None) -> Dict:
return self._request('POST', endpoint, json=json)
def put(self, endpoint: str, json: Dict = None) -> Dict:
return self._request('PUT', endpoint, json=json)
def patch(self, endpoint: str, json: Dict = None) -> Dict:
return self._request('PATCH', endpoint, json=json)
def delete(self, endpoint: str) -> Dict:
return self._request('DELETE', endpoint)Dashboard Operations
class DashboardMixin:
def search_dashboards(
self,
query: str = None,
tag: str = None,
folder_uid: str = None,
limit: int = 100
) -> List[Dict]:
params = {'type': 'dash-db', 'limit': limit}
if query:
params['query'] = query
if tag:
params['tag'] = tag
if folder_uid:
params['folderUIDs'] = folder_uid
return self.get('/api/search', params=params)
def get_dashboard_by_uid(self, uid: str) -> Dict:
return self.get(f'/api/dashboards/uid/{uid}')
def create_or_update_dashboard(
self,
dashboard: Dict,
folder_uid: str = None,
message: str = None,
overwrite: bool = False
) -> Dict:
payload = {
'dashboard': dashboard,
'overwrite': overwrite
}
if folder_uid:
payload['folderUid'] = folder_uid
if message:
payload['message'] = message
return self.post('/api/dashboards/db', json=payload)
def delete_dashboard(self, uid: str) -> Dict:
return self.delete(f'/api/dashboards/uid/{uid}')Data Source Operations
class DataSourceMixin:
def list_datasources(self) -> List[Dict]:
return self.get('/api/datasources')
def get_datasource_by_uid(self, uid: str) -> Dict:
return self.get(f'/api/datasources/uid/{uid}')
def create_datasource(self, datasource: Dict) -> Dict:
return self.post('/api/datasources', json=datasource)
def update_datasource(self, uid: str, datasource: Dict) -> Dict:
return self.put(f'/api/datasources/uid/{uid}', json=datasource)
def delete_datasource(self, uid: str) -> Dict:
return self.delete(f'/api/datasources/uid/{uid}')
def health_check_datasource(self, uid: str) -> Dict:
return self.get(f'/api/datasources/uid/{uid}/health')
def query_datasource(self, queries: List[Dict], from_time: str, to_time: str) -> Dict:
return self.post('/api/ds/query', json={
'queries': queries,
'from': from_time,
'to': to_time
})Complete Client
class GrafanaAPI(GrafanaClient, DashboardMixin, DataSourceMixin):
"""Complete Grafana API client with all mixins."""
pass
# Usage
grafana = GrafanaAPI(
base_url='https://grafana.example.com',
token='your-service-account-token'
)
# Search dashboards
dashboards = grafana.search_dashboards(query='production', tag='monitoring')
# Get dashboard details
dashboard = grafana.get_dashboard_by_uid('abc123')
# Create annotation
grafana.post('/api/annotations', json={
'dashboardUID': 'abc123',
'time': int(time.time() * 1000),
'tags': ['deploy'],
'text': 'Deployment completed'
})---
Bash Script Examples
Health Check Script
#!/bin/bash
GRAFANA_URL="${GRAFANA_URL:-http://localhost:3000}"
GRAFANA_TOKEN="${GRAFANA_TOKEN}"
check_health() {
response=$(curl -s -w "\n%{http_code}" \
-H "Authorization: Bearer $GRAFANA_TOKEN" \
"$GRAFANA_URL/api/health")
http_code=$(echo "$response" | tail -n1)
body=$(echo "$response" | head -n-1)
if [ "$http_code" = "200" ]; then
echo "✓ Grafana is healthy"
return 0
else
echo "✗ Grafana health check failed: $body"
return 1
fi
}
check_healthExport All Dashboards
#!/bin/bash
GRAFANA_URL="${GRAFANA_URL}"
GRAFANA_TOKEN="${GRAFANA_TOKEN}"
OUTPUT_DIR="${OUTPUT_DIR:-./dashboards}"
mkdir -p "$OUTPUT_DIR"
# Get all dashboard UIDs
uids=$(curl -s \
-H "Authorization: Bearer $GRAFANA_TOKEN" \
"$GRAFANA_URL/api/search?type=dash-db" | \
jq -r '.[].uid')
for uid in $uids; do
echo "Exporting dashboard: $uid"
curl -s \
-H "Authorization: Bearer $GRAFANA_TOKEN" \
"$GRAFANA_URL/api/dashboards/uid/$uid" | \
jq '.dashboard' > "$OUTPUT_DIR/$uid.json"
done
echo "Exported $(echo "$uids" | wc -w) dashboards to $OUTPUT_DIR"Import Dashboard
#!/bin/bash
GRAFANA_URL="${GRAFANA_URL}"
GRAFANA_TOKEN="${GRAFANA_TOKEN}"
DASHBOARD_FILE="${1}"
FOLDER_UID="${2:-}"
if [ -z "$DASHBOARD_FILE" ]; then
echo "Usage: $0 <dashboard.json> [folder_uid]"
exit 1
fi
# Read dashboard and wrap it
dashboard=$(cat "$DASHBOARD_FILE")
payload=$(jq -n \
--argjson dashboard "$dashboard" \
--arg folderUid "$FOLDER_UID" \
'{
dashboard: ($dashboard | .id = null | .uid = null),
folderUid: $folderUid,
overwrite: false
}')
response=$(curl -s -X POST \
-H "Authorization: Bearer $GRAFANA_TOKEN" \
-H "Content-Type: application/json" \
-d "$payload" \
"$GRAFANA_URL/api/dashboards/db")
echo "$response" | jq .Dashboards API Reference
Complete reference for Grafana Dashboard HTTP API endpoints.
Table of Contents
- Search Dashboards
- Get Dashboard by UID
- Create/Update Dashboard
- Delete Dashboard
- Get Dashboard Versions
- Restore Dashboard Version
- Dashboard Permissions
- Public/Shared Dashboards
---
Search Dashboards
GET /api/searchQuery Parameters:
| Parameter | Type | Description |
|---|---|---|
| query | string | Search query to filter by title |
| tag | string | Filter by tag (can be repeated) |
| type | string | dash-db for dashboards, dash-folder for folders |
| dashboardIds | array | List of dashboard IDs to search for |
| dashboardUIDs | array | List of dashboard UIDs to search for |
| folderIds | array | Filter by folder IDs |
| folderUIDs | array | Filter by folder UIDs |
| starred | boolean | Filter by starred dashboards |
| limit | integer | Maximum results (default: 1000) |
| page | integer | Page number for pagination |
| sort | string | alpha-asc, alpha-desc |
Example Request:
curl -H "Authorization: Bearer <TOKEN>" \
"https://grafana.example.com/api/search?type=dash-db&query=production&tag=monitoring&limit=50"Example Response:
[
{
"id": 163,
"uid": "cIBgcSjkk",
"title": "Production Overview",
"uri": "db/production-overview",
"url": "/d/cIBgcSjkk/production-overview",
"slug": "",
"type": "dash-db",
"tags": ["monitoring", "production"],
"isStarred": false,
"folderId": 3,
"folderUid": "l3KqBxCMz",
"folderTitle": "Operations",
"folderUrl": "/dashboards/f/l3KqBxCMz/operations",
"sortMeta": 0
}
]---
Get Dashboard by UID
GET /api/dashboards/uid/:uidExample Request:
curl -H "Authorization: Bearer <TOKEN>" \
"https://grafana.example.com/api/dashboards/uid/cIBgcSjkk"Example Response:
{
"meta": {
"type": "db",
"canSave": true,
"canEdit": true,
"canAdmin": true,
"canStar": true,
"canDelete": true,
"slug": "production-overview",
"url": "/d/cIBgcSjkk/production-overview",
"expires": "0001-01-01T00:00:00Z",
"created": "2023-01-15T10:30:00Z",
"updated": "2024-06-20T14:22:00Z",
"updatedBy": "admin",
"createdBy": "admin",
"version": 15,
"hasAcl": false,
"isFolder": false,
"folderId": 3,
"folderUid": "l3KqBxCMz",
"folderTitle": "Operations",
"folderUrl": "/dashboards/f/l3KqBxCMz/operations",
"provisioned": false,
"provisionedExternalId": ""
},
"dashboard": {
"id": 163,
"uid": "cIBgcSjkk",
"title": "Production Overview",
"tags": ["monitoring", "production"],
"timezone": "browser",
"schemaVersion": 38,
"version": 15,
"refresh": "30s",
"panels": [...]
}
}---
Create/Update Dashboard
POST /api/dashboards/dbRequest Body:
{
"dashboard": {
"id": null,
"uid": null,
"title": "New Dashboard",
"tags": ["tag1", "tag2"],
"timezone": "browser",
"schemaVersion": 38,
"refresh": "30s",
"panels": [
{
"id": 1,
"type": "timeseries",
"title": "CPU Usage",
"gridPos": {"h": 8, "w": 12, "x": 0, "y": 0},
"targets": [
{
"refId": "A",
"datasource": {"type": "prometheus", "uid": "prometheus"},
"expr": "100 - (avg(rate(node_cpu_seconds_total{mode=\"idle\"}[5m])) * 100)"
}
]
}
]
},
"folderUid": "l3KqBxCMz",
"message": "Initial commit",
"overwrite": false
}Fields:
| Field | Type | Description |
|---|---|---|
| dashboard.id | integer | Set to null for new dashboards |
| dashboard.uid | string | Unique identifier (auto-generated if null) |
| dashboard.title | string | Dashboard title (required) |
| dashboard.version | integer | Include for updates to prevent conflicts |
| folderUid | string | Target folder UID |
| folderId | integer | Target folder ID (deprecated, use folderUid) |
| message | string | Commit message for version history |
| overwrite | boolean | Force overwrite existing dashboard |
Example Response (Success):
{
"id": 163,
"uid": "cIBgcSjkk",
"url": "/d/cIBgcSjkk/new-dashboard",
"status": "success",
"version": 1,
"slug": "new-dashboard"
}---
Delete Dashboard
DELETE /api/dashboards/uid/:uidExample Request:
curl -X DELETE -H "Authorization: Bearer <TOKEN>" \
"https://grafana.example.com/api/dashboards/uid/cIBgcSjkk"Example Response:
{
"title": "Production Overview",
"message": "Dashboard Production Overview deleted",
"id": 163
}---
Get Dashboard Versions
GET /api/dashboards/uid/:uid/versionsQuery Parameters:
| Parameter | Type | Description |
|---|---|---|
| limit | integer | Max versions to return (default: 0 = all) |
| start | integer | Start index for pagination |
Example Response:
[
{
"id": 15,
"dashboardId": 163,
"parentVersion": 14,
"restoredFrom": 0,
"version": 15,
"created": "2024-06-20T14:22:00Z",
"createdBy": "admin",
"message": "Updated thresholds"
}
]---
Restore Dashboard Version
POST /api/dashboards/uid/:uid/restoreRequest Body:
{
"version": 10
}Example Response:
{
"id": 163,
"uid": "cIBgcSjkk",
"url": "/d/cIBgcSjkk/production-overview",
"status": "success",
"version": 16,
"slug": "production-overview"
}---
Dashboard Permissions
Get Permissions
GET /api/dashboards/uid/:uid/permissionsUpdate Permissions
POST /api/dashboards/uid/:uid/permissionsRequest Body:
{
"items": [
{"role": "Viewer", "permission": 1},
{"role": "Editor", "permission": 2},
{"teamId": 1, "permission": 1},
{"userId": 11, "permission": 4}
]
}Permission Levels:
1: View2: Edit4: Admin
---
Public/Shared Dashboards
Create Public Dashboard
POST /api/dashboards/uid/:uid/public-dashboards/Request Body:
{
"uid": "cd56d9fd-f3d4-486d-afba-a21760e2acbe",
"accessToken": "5c948bf96e6a4b13bd91975f9a2028b7",
"timeSelectionEnabled": false,
"isEnabled": true,
"annotationsEnabled": false,
"share": "public"
}Get Public Dashboard
GET /api/dashboards/uid/:uid/public-dashboards/Update Public Dashboard
PATCH /api/dashboards/uid/:uid/public-dashboards/:publicDashboardUidDelete Public Dashboard
DELETE /api/dashboards/uid/:uid/public-dashboards/:publicDashboardUidData Sources API Reference
Complete reference for Grafana Data Source HTTP API endpoints.
Table of Contents
- List Data Sources
- Get Data Source
- Create Data Source
- Update Data Source
- Delete Data Source
- Query Data Source
- Health Check
- Data Source Resources
---
List Data Sources
GET /api/datasourcesNote: Default max returned is 5000. Pagination not currently supported.
Example Response:
[
{
"id": 1,
"uid": "PE9C8AA5B1A6E7E89",
"orgId": 1,
"name": "Prometheus",
"type": "prometheus",
"typeName": "Prometheus",
"typeLogoUrl": "public/app/plugins/datasource/prometheus/img/prometheus_logo.svg",
"access": "proxy",
"url": "http://prometheus:9090",
"user": "",
"database": "",
"basicAuth": false,
"isDefault": true,
"jsonData": {
"httpMethod": "POST",
"manageAlerts": true,
"prometheusType": "Prometheus"
},
"readOnly": false
}
]---
Get Data Source
By ID (Deprecated)
GET /api/datasources/:idBy UID (Recommended)
GET /api/datasources/uid/:uidBy Name
GET /api/datasources/name/:nameExample Response:
{
"id": 1,
"uid": "PE9C8AA5B1A6E7E89",
"orgId": 1,
"name": "Prometheus",
"type": "prometheus",
"access": "proxy",
"url": "http://prometheus:9090",
"basicAuth": false,
"isDefault": true,
"jsonData": {
"httpMethod": "POST",
"manageAlerts": true
},
"secureJsonFields": {},
"version": 1,
"readOnly": false
}---
Create Data Source
POST /api/datasourcesPrometheus Example
{
"name": "Prometheus",
"type": "prometheus",
"url": "http://prometheus:9090",
"access": "proxy",
"basicAuth": false,
"isDefault": true,
"jsonData": {
"httpMethod": "POST",
"manageAlerts": true,
"prometheusType": "Prometheus",
"prometheusVersion": "2.47.0"
}
}InfluxDB Example
{
"name": "InfluxDB",
"type": "influxdb",
"url": "http://influxdb:8086",
"access": "proxy",
"basicAuth": false,
"database": "telegraf",
"jsonData": {
"httpMode": "POST",
"version": "Flux"
},
"secureJsonData": {
"token": "your-influxdb-token"
}
}PostgreSQL Example
{
"name": "PostgreSQL",
"type": "postgres",
"url": "postgres:5432",
"access": "proxy",
"user": "grafana",
"database": "grafana",
"basicAuth": false,
"jsonData": {
"sslmode": "disable",
"maxOpenConns": 100,
"maxIdleConns": 100,
"connMaxLifetime": 14400
},
"secureJsonData": {
"password": "your-password"
}
}Loki Example
{
"name": "Loki",
"type": "loki",
"url": "http://loki:3100",
"access": "proxy",
"basicAuth": false,
"jsonData": {
"maxLines": 1000,
"derivedFields": [
{
"matcherRegex": "traceID=(\\w+)",
"name": "TraceID",
"url": "${__value.raw}",
"datasourceUid": "tempo-uid"
}
]
}
}CloudWatch Example
{
"name": "CloudWatch",
"type": "cloudwatch",
"access": "proxy",
"jsonData": {
"authType": "default",
"defaultRegion": "us-east-1"
}
}Azure Monitor Example
{
"name": "Azure Monitor",
"type": "grafana-azure-monitor-datasource",
"access": "proxy",
"jsonData": {
"cloudName": "azuremonitor",
"tenantId": "your-tenant-id",
"clientId": "your-client-id",
"subscriptionId": "your-subscription-id"
},
"secureJsonData": {
"clientSecret": "your-client-secret"
}
}---
Update Data Source
By ID (Deprecated)
PUT /api/datasources/:idBy UID (Recommended)
PUT /api/datasources/uid/:uidRequest Body: Same as create, include all fields.
---
Delete Data Source
By ID (Deprecated)
DELETE /api/datasources/:idBy UID (Recommended)
DELETE /api/datasources/uid/:uidBy Name
DELETE /api/datasources/name/:name---
Query Data Source
POST /api/ds/queryExecute queries against any data source with a backend implementation.
Prometheus Query Example
{
"queries": [
{
"refId": "A",
"datasource": {
"type": "prometheus",
"uid": "PE9C8AA5B1A6E7E89"
},
"expr": "up{job=\"prometheus\"}",
"instant": false,
"range": true,
"intervalMs": 15000,
"maxDataPoints": 1000
}
],
"from": "now-1h",
"to": "now"
}Loki Query Example
{
"queries": [
{
"refId": "A",
"datasource": {
"type": "loki",
"uid": "loki-uid"
},
"expr": "{job=\"nginx\"} |= \"error\"",
"queryType": "range",
"maxLines": 1000
}
],
"from": "now-1h",
"to": "now"
}SQL Query Example
{
"queries": [
{
"refId": "A",
"datasource": {
"type": "postgres",
"uid": "postgres-uid"
},
"rawSql": "SELECT time, value FROM metrics WHERE $__timeFilter(time)",
"format": "time_series"
}
],
"from": "now-1h",
"to": "now"
}Response Structure:
{
"results": {
"A": {
"frames": [
{
"schema": {
"refId": "A",
"fields": [
{"name": "time", "type": "time"},
{"name": "value", "type": "number"}
]
},
"data": {
"values": [
[1644488152084, 1644488212084],
[0.95, 0.97]
]
}
}
]
}
}
}---
Health Check
GET /api/datasources/uid/:uid/healthExample Response (Success):
{
"status": "OK",
"message": "Successfully connected to Prometheus"
}Example Response (Error):
{
"status": "ERROR",
"message": "Post \"http://prometheus:9090/api/v1/query\": dial tcp: connection refused"
}---
Data Source Resources
Access data source-specific resources (metrics, dimensions, etc.).
GET /api/datasources/uid/:uid/resources/:resourceCloudWatch Dimension Keys Example
GET /api/datasources/uid/cloudwatch-uid/resources/dimension-keys?region=us-east-1&namespace=AWS/EC2Prometheus Label Values Example
GET /api/datasources/uid/prometheus-uid/resources/api/v1/label/__name__/values---
Common Data Source Types
| Type | Plugin ID |
|---|---|
| Prometheus | prometheus |
| Loki | loki |
| InfluxDB | influxdb |
| PostgreSQL | postgres |
| MySQL | mysql |
| Elasticsearch | elasticsearch |
| CloudWatch | cloudwatch |
| Azure Monitor | grafana-azure-monitor-datasource |
| Google Cloud Monitoring | stackdriver |
| Graphite | graphite |
| Tempo | tempo |
| Jaeger | jaeger |
| Zipkin | zipkin |
Folders API Reference
Complete reference for Grafana Folder HTTP API endpoints.
Table of Contents
---
List Folders
GET /api/foldersQuery Parameters:
| Parameter | Type | Description |
|---|---|---|
| limit | integer | Max folders to return (default: 1000) |
| page | integer | Page number for pagination |
Example Response:
[
{
"id": 1,
"uid": "nErXDvCkzz",
"title": "Operations",
"url": "/dashboards/f/nErXDvCkzz/operations",
"hasAcl": false,
"canSave": true,
"canEdit": true,
"canAdmin": true,
"canDelete": true,
"createdBy": "admin",
"created": "2023-01-15T10:30:00Z",
"updatedBy": "admin",
"updated": "2024-06-20T14:22:00Z",
"version": 1
}
]---
Get Folder
By UID
GET /api/folders/:uidBy ID (Deprecated)
GET /api/folders/id/:idExample Response:
{
"id": 1,
"uid": "nErXDvCkzz",
"title": "Operations",
"url": "/dashboards/f/nErXDvCkzz/operations",
"hasAcl": false,
"canSave": true,
"canEdit": true,
"canAdmin": true,
"canDelete": true,
"createdBy": "admin",
"created": "2023-01-15T10:30:00Z",
"updatedBy": "admin",
"updated": "2024-06-20T14:22:00Z",
"version": 3,
"parentUid": ""
}---
Create Folder
POST /api/foldersRequest Body:
{
"uid": "my-folder-uid",
"title": "My New Folder",
"parentUid": "parent-folder-uid"
}Fields:
| Field | Type | Required | Description |
|---|---|---|---|
| title | string | Yes | Folder title |
| uid | string | No | Unique identifier (auto-generated if omitted) |
| parentUid | string | No | Parent folder UID (for nested folders) |
Example Response:
{
"id": 5,
"uid": "my-folder-uid",
"title": "My New Folder",
"url": "/dashboards/f/my-folder-uid/my-new-folder",
"hasAcl": false,
"canSave": true,
"canEdit": true,
"canAdmin": true,
"canDelete": true,
"createdBy": "admin",
"created": "2024-06-20T14:22:00Z",
"updatedBy": "admin",
"updated": "2024-06-20T14:22:00Z",
"version": 1,
"parentUid": "parent-folder-uid"
}---
Update Folder
PUT /api/folders/:uidRequest Body:
{
"title": "Updated Folder Title",
"version": 1,
"overwrite": false
}Fields:
| Field | Type | Required | Description |
|---|---|---|---|
| title | string | Yes | New folder title |
| version | integer | Yes | Current version (for optimistic locking) |
| overwrite | boolean | No | Force update regardless of version |
Error Response (Version Mismatch):
{
"message": "The folder has been changed by someone else",
"status": "version-mismatch"
}---
Delete Folder
DELETE /api/folders/:uidQuery Parameters:
| Parameter | Type | Default | Description |
|---|---|---|---|
| forceDeleteRules | boolean | false | Delete alert rules in folder |
Warning: Deleting a folder also deletes all dashboards inside it. This operation cannot be undone.
Example Request:
curl -X DELETE -H "Authorization: Bearer <TOKEN>" \
"https://grafana.example.com/api/folders/nErXDvCkzz?forceDeleteRules=true"Example Response:
{
"title": "Operations",
"message": "Folder Operations deleted",
"id": 1
}---
Move Folder
POST /api/folders/:uid/moveNote: Only available when nested folders are enabled.
Request Body:
{
"parentUid": "new-parent-folder-uid"
}Use empty string or omit parentUid to move to root level.
---
Folder Permissions
Get Folder Permissions
GET /api/folders/:uid/permissionsExample Response:
[
{
"id": 1,
"folderId": 1,
"created": "2023-01-15T10:30:00Z",
"updated": "2024-06-20T14:22:00Z",
"userId": 0,
"userLogin": "",
"userEmail": "",
"teamId": 0,
"team": "",
"role": "Viewer",
"permission": 1,
"permissionName": "View",
"uid": "nErXDvCkzz",
"title": "Operations",
"slug": "operations",
"isFolder": true,
"url": "/dashboards/f/nErXDvCkzz/operations",
"inherited": false
}
]Update Folder Permissions
POST /api/folders/:uid/permissionsRequest Body:
{
"items": [
{"role": "Viewer", "permission": 1},
{"role": "Editor", "permission": 2},
{"teamId": 1, "permission": 1},
{"userId": 11, "permission": 4}
]
}Permission Levels:
| Value | Name | Description |
|---|---|---|
| 1 | View | Can view dashboards in folder |
| 2 | Edit | Can edit dashboards in folder |
| 4 | Admin | Full admin rights to folder |
---
Search Folders and Dashboards
GET /api/searchQuery Parameters for Folder Search:
| Parameter | Type | Description |
|---|---|---|
| type | string | dash-folder for folders only |
| query | string | Search by title |
| folderIds | array | Filter by parent folder IDs |
Example:
curl -H "Authorization: Bearer <TOKEN>" \
"https://grafana.example.com/api/search?type=dash-folder&query=prod"---
New Folder API (v1beta1)
Grafana is transitioning to a new Kubernetes-style API structure.
List Folders (New API)
GET /apis/folder.grafana.app/v1beta1/namespaces/default/foldersGet Folder (New API)
GET /apis/folder.grafana.app/v1beta1/namespaces/default/folders/:uidCreate Folder (New API)
POST /apis/folder.grafana.app/v1beta1/namespaces/default/foldersRequest Body:
{
"metadata": {
"name": "my-folder-uid",
"annotations": {
"grafana.app/folder": "parent-folder-uid"
}
},
"spec": {
"title": "My New Folder"
}
}Response:
{
"kind": "Folder",
"apiVersion": "folder.grafana.app/v1beta1",
"metadata": {
"name": "my-folder-uid",
"namespace": "default",
"uid": "...",
"resourceVersion": "...",
"creationTimestamp": "2024-06-20T14:22:00Z",
"annotations": {
"grafana.app/folder": "parent-folder-uid",
"grafana.app/createdBy": "admin",
"grafana.app/updatedBy": "admin",
"grafana.app/updatedTimestamp": "2024-06-20T14:22:00Z"
}
},
"spec": {
"title": "My New Folder"
}
}Users & Teams API Reference
Complete reference for Grafana User, Team, and Service Account HTTP API endpoints.
Table of Contents
- Current User
- Users (Admin)
- Teams
- Team Members
- Service Accounts
- Service Account Tokens
- Organizations
- Organization Users
---
Current User
Get Current User
GET /api/userExample Response:
{
"id": 1,
"email": "admin@example.com",
"name": "Admin User",
"login": "admin",
"theme": "dark",
"orgId": 1,
"isGrafanaAdmin": true,
"isDisabled": false,
"isExternal": false,
"authLabels": [],
"updatedAt": "2024-06-20T14:22:00Z",
"createdAt": "2023-01-15T10:30:00Z",
"avatarUrl": "/avatar/46d229b033af06a191ff2267bca9ae56"
}Update Current User
PUT /api/user{
"name": "New Name",
"email": "newemail@example.com",
"login": "newlogin",
"theme": "light"
}Get Current User Organizations
GET /api/user/orgsGet Current User Teams
GET /api/user/teamsStar/Unstar Dashboard
POST /api/user/stars/dashboard/uid/:dashboardUID
DELETE /api/user/stars/dashboard/uid/:dashboardUIDChange Active Organization
POST /api/user/using/:orgId---
Users (Admin)
Requires Grafana Admin permission.
Search Users
GET /api/users/searchQuery Parameters:
| Parameter | Type | Description |
|---|---|---|
| perpage | integer | Results per page (default: 1000) |
| page | integer | Page number |
| query | string | Search by login, email, or name |
| sort | string | Sort order (e.g., login-asc, email-desc) |
Example Response:
{
"totalCount": 2,
"users": [
{
"id": 1,
"name": "Admin User",
"login": "admin",
"email": "admin@example.com",
"isAdmin": true,
"isDisabled": false,
"lastSeenAt": "2024-06-20T14:22:00Z",
"lastSeenAtAge": "2m",
"authLabels": ["OAuth"]
}
],
"page": 1,
"perPage": 10
}Get User by ID
GET /api/users/:idCreate User
POST /api/admin/users{
"name": "New User",
"email": "newuser@example.com",
"login": "newuser",
"password": "password123",
"OrgId": 1
}Update User
PUT /api/users/:id{
"name": "Updated Name",
"email": "updated@example.com",
"login": "updatedlogin",
"theme": "dark"
}Delete User
DELETE /api/admin/users/:idUpdate User Permissions
PUT /api/admin/users/:id/permissions{
"isGrafanaAdmin": true
}Disable/Enable User
POST /api/admin/users/:id/disable
POST /api/admin/users/:id/enable---
Teams
Search Teams
GET /api/teams/searchQuery Parameters:
| Parameter | Type | Description |
|---|---|---|
| perpage | integer | Results per page (default: 1000) |
| page | integer | Page number |
| name | string | Filter by team name |
| query | string | Search query |
Example Response:
{
"totalCount": 1,
"teams": [
{
"id": 1,
"orgId": 1,
"name": "Platform Team",
"email": "platform@example.com",
"avatarUrl": "/avatar/3f49c15916554246daa714b9bd0ee398",
"memberCount": 5,
"permission": 0
}
],
"page": 1,
"perPage": 1000
}Get Team by ID
GET /api/teams/:teamIdCreate Team
POST /api/teams{
"name": "DevOps Team",
"email": "devops@example.com"
}Update Team
PUT /api/teams/:teamId{
"name": "Updated Team Name",
"email": "newemail@example.com"
}Delete Team
DELETE /api/teams/:teamId---
Team Members
Get Team Members
GET /api/teams/:teamId/membersExample Response:
[
{
"orgId": 1,
"teamId": 1,
"userId": 2,
"email": "user@example.com",
"name": "User Name",
"login": "username",
"avatarUrl": "/avatar/46d229b033af06a191ff2267bca9ae56",
"labels": [],
"permission": 0
}
]Add Team Member
POST /api/teams/:teamId/members{
"userId": 5
}Remove Team Member
DELETE /api/teams/:teamId/members/:userIdUpdate Team Member Permission
PUT /api/teams/:teamId/members/:userId{
"permission": 4
}Permission Values:
0: Member4: Admin
---
Service Accounts
Search Service Accounts
GET /api/serviceaccounts/searchQuery Parameters:
| Parameter | Type | Description |
|---|---|---|
| perpage | integer | Results per page |
| page | integer | Page number |
| query | string | Search query |
| disabled | boolean | Filter by disabled status |
Example Response:
{
"totalCount": 1,
"serviceAccounts": [
{
"id": 1,
"name": "automation-sa",
"login": "sa-automation-sa",
"orgId": 1,
"isDisabled": false,
"role": "Editor",
"tokens": 2,
"avatarUrl": "/avatar/85ec38023d90823d3e5b43ef35646af9"
}
],
"page": 1,
"perPage": 10
}Get Service Account by ID
GET /api/serviceaccounts/:idCreate Service Account
POST /api/serviceaccounts{
"name": "automation-sa",
"role": "Editor",
"isDisabled": false
}Roles: Viewer, Editor, Admin
Update Service Account
PATCH /api/serviceaccounts/:id{
"name": "new-name",
"role": "Admin",
"isDisabled": false
}Delete Service Account
DELETE /api/serviceaccounts/:id---
Service Account Tokens
List Tokens
GET /api/serviceaccounts/:id/tokensExample Response:
[
{
"id": 1,
"name": "token-1",
"created": "2024-06-20T14:22:00Z",
"expiration": "2024-12-20T14:22:00Z",
"secondsUntilExpiration": 15552000,
"hasExpired": false,
"lastUsedAt": "2024-06-20T14:22:00Z"
}
]Create Token
POST /api/serviceaccounts/:id/tokens{
"name": "automation-token",
"secondsToLive": 86400
}Use secondsToLive: 0 for non-expiring tokens.
Response:
{
"id": 2,
"name": "automation-token",
"key": "glsa_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
}Important: The key is only shown once. Store it securely.
Delete Token
DELETE /api/serviceaccounts/:id/tokens/:tokenId---
Organizations
Get Current Organization
GET /api/orgUpdate Current Organization
PUT /api/org{
"name": "New Org Name"
}List Organizations (Admin)
GET /api/orgsCreate Organization (Admin)
POST /api/orgs{
"name": "New Organization"
}Get Organization by ID (Admin)
GET /api/orgs/:orgIdUpdate Organization (Admin)
PUT /api/orgs/:orgIdDelete Organization (Admin)
DELETE /api/orgs/:orgId---
Organization Users
Get Current Org Users
GET /api/org/usersExample Response:
[
{
"orgId": 1,
"userId": 1,
"email": "admin@example.com",
"name": "Admin",
"avatarUrl": "/avatar/46d229b033af06a191ff2267bca9ae56",
"login": "admin",
"role": "Admin",
"lastSeenAt": "2024-06-20T14:22:00Z",
"lastSeenAtAge": "2m"
}
]Add User to Current Org
POST /api/org/users{
"loginOrEmail": "user@example.com",
"role": "Viewer"
}Update User Role in Current Org
PATCH /api/org/users/:userId{
"role": "Editor"
}Remove User from Current Org
DELETE /api/org/users/:userIdGet Org Users (Admin)
GET /api/orgs/:orgId/usersAdd User to Org (Admin)
POST /api/orgs/:orgId/users{
"loginOrEmail": "user@example.com",
"role": "Viewer"
}Update User Role in Org (Admin)
PATCH /api/orgs/:orgId/users/:userIdRemove User from Org (Admin)
DELETE /api/orgs/:orgId/users/:userId#!/usr/bin/env bun
/**
* DashboardCrud - CLI tool for Grafana Dashboard CRUD operations
*
* @example
* ```bash
* bun run DashboardCrud.ts list
* bun run DashboardCrud.ts get abc123
* bun run DashboardCrud.ts create --file dashboard.json --folder my-folder
* bun run DashboardCrud.ts delete abc123
* ```
*/
import {
GrafanaClient,
createGrafanaClient,
type DashboardModel,
type DashboardSearchResult,
type DashboardResponse,
type SaveDashboardResponse,
type DashboardVersion,
type GrafanaApiError,
} from './GrafanaClient';
// =============================================================================
// Types
// =============================================================================
interface CliOptions {
url?: string;
token?: string;
orgId?: number;
output?: string;
file?: string;
folder?: string;
title?: string;
message?: string;
overwrite?: boolean;
query?: string;
tag?: string;
limit?: number;
version?: number;
json?: boolean;
quiet?: boolean;
}
type Command = 'list' | 'get' | 'create' | 'update' | 'delete' | 'export' | 'import' | 'clone' | 'versions' | 'restore' | 'help';
// =============================================================================
// Utilities
// =============================================================================
function parseArgs(args: string[]): { command: Command; uid?: string; options: CliOptions } {
const options: CliOptions = {};
let command: Command = 'help';
let uid: string | undefined;
for (let i = 0; i < args.length; i++) {
const arg = args[i];
if (!arg.startsWith('-')) {
if (!command || command === 'help') {
command = arg as Command;
} else if (!uid) {
uid = arg;
}
continue;
}
const nextArg = args[i + 1];
switch (arg) {
case '--url': case '-u': options.url = nextArg; i++; break;
case '--token': case '-t': options.token = nextArg; i++; break;
case '--org-id': case '-o': options.orgId = parseInt(nextArg); i++; break;
case '--output': options.output = nextArg; i++; break;
case '--file': case '-f': options.file = nextArg; i++; break;
case '--folder': options.folder = nextArg; i++; break;
case '--title': options.title = nextArg; i++; break;
case '--message': case '-m': options.message = nextArg; i++; break;
case '--overwrite': options.overwrite = true; break;
case '--query': case '-q': options.query = nextArg; i++; break;
case '--tag': options.tag = nextArg; i++; break;
case '--limit': case '-l': options.limit = parseInt(nextArg); i++; break;
case '--version': case '-v': options.version = parseInt(nextArg); i++; break;
case '--json': case '-j': options.json = true; break;
case '--quiet': options.quiet = true; break;
case '--help': case '-h': command = 'help'; break;
}
}
return { command, uid, options };
}
function error(message: string): never {
console.error(`Error: ${message}`);
process.exit(1);
}
async function readJsonFile(path: string): Promise<unknown> {
try {
const file = Bun.file(path);
return await file.json();
} catch {
error(`Failed to read JSON file: ${path}`);
}
}
async function writeJsonFile(path: string, data: unknown): Promise<void> {
try {
await Bun.write(path, JSON.stringify(data, null, 2));
} catch {
error(`Failed to write JSON file: ${path}`);
}
}
// =============================================================================
// Commands
// =============================================================================
async function listDashboards(client: GrafanaClient, options: CliOptions): Promise<DashboardSearchResult[]> {
const results = await client.searchDashboards({
query: options.query,
tag: options.tag,
limit: options.limit,
});
if (!options.json) {
console.log('\n📊 Dashboards:\n');
console.log('─'.repeat(80));
if (results.length === 0) {
console.log('No dashboards found.');
} else {
results.forEach((dash) => {
const tags = dash.tags.length > 0 ? ` [${dash.tags.join(', ')}]` : '';
const folder = dash.folderTitle ? ` (${dash.folderTitle})` : '';
console.log(` ${dash.uid} ${dash.title}${folder}${tags}`);
});
}
console.log('─'.repeat(80));
console.log(`Total: ${results.length} dashboards\n`);
} else {
console.log(JSON.stringify(results, null, 2));
}
return results;
}
async function getDashboard(client: GrafanaClient, uid: string, options: CliOptions): Promise<DashboardResponse> {
const result = await client.getDashboardByUid(uid);
if (!options.json) {
console.log('\n📊 Dashboard Details:\n');
console.log('─'.repeat(80));
console.log(` UID: ${result.dashboard.uid}`);
console.log(` Title: ${result.dashboard.title}`);
console.log(` Version: ${result.meta.version}`);
console.log(` Folder: ${result.meta.folderTitle ?? 'General'}`);
console.log(` URL: ${result.meta.url}`);
console.log(` Created: ${result.meta.created}`);
console.log(` Updated: ${result.meta.updated}`);
console.log(` Panels: ${result.dashboard.panels?.length ?? 0}`);
if (result.dashboard.tags?.length) {
console.log(` Tags: ${result.dashboard.tags.join(', ')}`);
}
console.log('─'.repeat(80));
} else {
console.log(JSON.stringify(result, null, 2));
}
return result;
}
async function createDashboard(client: GrafanaClient, options: CliOptions): Promise<SaveDashboardResponse> {
if (!options.file) error('--file is required for create command');
const dashboardJson = (await readJsonFile(options.file)) as DashboardModel | { dashboard: DashboardModel };
const dashboard: DashboardModel = 'dashboard' in dashboardJson ? dashboardJson.dashboard : dashboardJson;
if (options.title) dashboard.title = options.title;
dashboard.id = null;
dashboard.uid = null;
const result = await client.saveDashboard({
dashboard,
folderUid: options.folder,
message: options.message ?? 'Created via CLI',
overwrite: options.overwrite ?? false,
});
if (!options.json) {
console.log('\n✅ Dashboard Created:\n');
console.log('─'.repeat(80));
console.log(` UID: ${result.uid}`);
console.log(` URL: ${result.url}`);
console.log(` Version: ${result.version}`);
console.log('─'.repeat(80));
} else {
console.log(JSON.stringify(result, null, 2));
}
return result;
}
async function updateDashboard(client: GrafanaClient, uid: string, options: CliOptions): Promise<SaveDashboardResponse> {
if (!options.file) error('--file is required for update command');
const existing = await client.getDashboardByUid(uid);
const dashboardJson = (await readJsonFile(options.file)) as DashboardModel | { dashboard: DashboardModel };
const dashboard: DashboardModel = 'dashboard' in dashboardJson ? dashboardJson.dashboard : dashboardJson;
dashboard.uid = uid;
dashboard.version = existing.dashboard.version;
if (options.title) dashboard.title = options.title;
const result = await client.saveDashboard({
dashboard,
folderUid: options.folder ?? existing.meta.folderUid,
message: options.message ?? 'Updated via CLI',
overwrite: options.overwrite ?? false,
});
if (!options.json) {
console.log('\n✅ Dashboard Updated:\n');
console.log('─'.repeat(80));
console.log(` UID: ${result.uid}`);
console.log(` URL: ${result.url}`);
console.log(` Version: ${result.version}`);
console.log('─'.repeat(80));
} else {
console.log(JSON.stringify(result, null, 2));
}
return result;
}
async function deleteDashboard(client: GrafanaClient, uid: string, options: CliOptions): Promise<void> {
const result = await client.deleteDashboard(uid);
if (!options.json) {
console.log('\n🗑️ Dashboard Deleted:\n');
console.log('─'.repeat(80));
console.log(` Title: ${result.title}`);
console.log(` Message: ${result.message}`);
console.log('─'.repeat(80));
} else {
console.log(JSON.stringify(result, null, 2));
}
}
async function exportDashboard(client: GrafanaClient, uid: string, options: CliOptions): Promise<void> {
const result = await client.getDashboardByUid(uid);
const outputPath = options.output ?? `${uid}.json`;
await writeJsonFile(outputPath, result.dashboard);
if (!options.quiet) {
console.log(`\n✅ Dashboard exported to: ${outputPath}`);
}
}
async function importDashboard(client: GrafanaClient, options: CliOptions): Promise<SaveDashboardResponse> {
if (!options.file) error('--file is required for import command');
const dashboardJson = (await readJsonFile(options.file)) as DashboardModel | { dashboard: DashboardModel; folderUid?: string };
let dashboard: DashboardModel;
let folderUid: string | undefined;
if ('dashboard' in dashboardJson) {
dashboard = dashboardJson.dashboard;
folderUid = dashboardJson.folderUid ?? options.folder;
} else {
dashboard = dashboardJson;
folderUid = options.folder;
}
if (options.title) dashboard.title = options.title;
dashboard.id = null;
if (!dashboard.uid) dashboard.uid = null;
const result = await client.saveDashboard({
dashboard,
folderUid,
message: options.message ?? 'Imported via CLI',
overwrite: options.overwrite ?? false,
});
if (!options.json) {
console.log('\n✅ Dashboard Imported:\n');
console.log('─'.repeat(80));
console.log(` UID: ${result.uid}`);
console.log(` URL: ${result.url}`);
console.log(` Version: ${result.version}`);
console.log('─'.repeat(80));
} else {
console.log(JSON.stringify(result, null, 2));
}
return result;
}
async function cloneDashboard(client: GrafanaClient, uid: string, options: CliOptions): Promise<SaveDashboardResponse> {
const source = await client.getDashboardByUid(uid);
const dashboard = { ...source.dashboard };
dashboard.id = null;
dashboard.uid = null;
dashboard.title = options.title ?? `${source.dashboard.title} (Copy)`;
const result = await client.saveDashboard({
dashboard,
folderUid: options.folder ?? source.meta.folderUid,
message: options.message ?? `Cloned from ${uid}`,
overwrite: false,
});
if (!options.json) {
console.log('\n✅ Dashboard Cloned:\n');
console.log('─'.repeat(80));
console.log(` Source: ${uid}`);
console.log(` New UID: ${result.uid}`);
console.log(` Title: ${dashboard.title}`);
console.log(` URL: ${result.url}`);
console.log('─'.repeat(80));
} else {
console.log(JSON.stringify(result, null, 2));
}
return result;
}
async function listVersions(client: GrafanaClient, uid: string, options: CliOptions): Promise<DashboardVersion[]> {
const versions = await client.getDashboardVersions(uid, options.limit);
if (!options.json) {
console.log('\n📋 Dashboard Versions:\n');
console.log('─'.repeat(80));
versions.forEach((v) => {
const restored = v.restoredFrom > 0 ? ` (restored from v${v.restoredFrom})` : '';
console.log(` v${v.version.toString().padStart(3)} ${v.created} by ${v.createdBy} ${v.message ?? ''}${restored}`);
});
console.log('─'.repeat(80));
console.log(`Total: ${versions.length} versions\n`);
} else {
console.log(JSON.stringify(versions, null, 2));
}
return versions;
}
async function restoreVersion(client: GrafanaClient, uid: string, options: CliOptions): Promise<SaveDashboardResponse> {
if (!options.version) error('--version is required for restore command');
const result = await client.restoreDashboardVersion(uid, options.version);
if (!options.json) {
console.log('\n✅ Dashboard Restored:\n');
console.log('─'.repeat(80));
console.log(` UID: ${result.uid}`);
console.log(` Restored to: v${options.version}`);
console.log(` New version: v${result.version}`);
console.log(` URL: ${result.url}`);
console.log('─'.repeat(80));
} else {
console.log(JSON.stringify(result, null, 2));
}
return result;
}
function showHelp(): void {
console.log(`
╔══════════════════════════════════════════════════════════════════════════════╗
║ Grafana Dashboard CRUD CLI ║
╚══════════════════════════════════════════════════════════════════════════════╝
USAGE:
bun run DashboardCrud.ts <command> [uid] [options]
COMMANDS:
list List/search dashboards
get <uid> Get dashboard by UID
create Create new dashboard from JSON file
update <uid> Update existing dashboard from JSON file
delete <uid> Delete dashboard by UID
export <uid> Export dashboard to JSON file
import Import dashboard from JSON file
clone <uid> Clone an existing dashboard
versions <uid> List dashboard version history
restore <uid> Restore dashboard to a specific version
help Show this help message
GLOBAL OPTIONS:
--url, -u Grafana URL (default: GRAFANA_URL env var)
--token, -t Service account token (default: GRAFANA_TOKEN env var)
--org-id, -o Organization ID (default: GRAFANA_ORG_ID env var)
--json, -j Output as JSON
--quiet Suppress non-essential output
COMMAND OPTIONS:
--file, -f Input JSON file (for create/update/import)
--output Output file path (for export, default: <uid>.json)
--folder Target folder UID
--title Dashboard title (overrides JSON)
--message, -m Commit message for version history
--overwrite Force overwrite existing dashboard
--query, -q Search query (for list)
--tag Filter by tag (for list)
--limit, -l Limit results (for list/versions)
--version, -v Version number (for restore)
EXAMPLES:
bun run DashboardCrud.ts list --query production --tag monitoring
bun run DashboardCrud.ts get abc123
bun run DashboardCrud.ts export abc123 --output my-dashboard.json
bun run DashboardCrud.ts create --file dashboard.json --folder my-folder
bun run DashboardCrud.ts clone abc123 --title "Production Copy"
bun run DashboardCrud.ts restore abc123 --version 5
ENVIRONMENT VARIABLES:
GRAFANA_URL Grafana instance URL
GRAFANA_TOKEN Service account token
GRAFANA_ORG_ID Organization ID (optional)
`);
}
// =============================================================================
// Main
// =============================================================================
async function main(): Promise<void> {
const args = process.argv.slice(2);
const { command, uid, options } = parseArgs(args);
if (command === 'help' || args.length === 0) {
showHelp();
return;
}
let client: GrafanaClient;
try {
client = createGrafanaClient({
baseUrl: options.url,
token: options.token,
orgId: options.orgId,
});
} catch (err) {
error((err as Error).message);
}
try {
switch (command) {
case 'list': await listDashboards(client, options); break;
case 'get': if (!uid) error('Dashboard UID required'); await getDashboard(client, uid!, options); break;
case 'create': await createDashboard(client, options); break;
case 'update': if (!uid) error('Dashboard UID required'); await updateDashboard(client, uid!, options); break;
case 'delete': if (!uid) error('Dashboard UID required'); await deleteDashboard(client, uid!, options); break;
case 'export': if (!uid) error('Dashboard UID required'); await exportDashboard(client, uid!, options); break;
case 'import': await importDashboard(client, options); break;
case 'clone': if (!uid) error('Source dashboard UID required'); await cloneDashboard(client, uid!, options); break;
case 'versions': if (!uid) error('Dashboard UID required'); await listVersions(client, uid!, options); break;
case 'restore': if (!uid) error('Dashboard UID required'); await restoreVersion(client, uid!, options); break;
default: error(`Unknown command: ${command}`);
}
} catch (err) {
const apiError = err as GrafanaApiError;
if (apiError.statusCode) {
error(`[${apiError.statusCode}] ${apiError.message}`);
} else {
error((err as Error).message);
}
}
}
main();
#!/usr/bin/env bun
/**
* GrafanaClient - TypeScript HTTP API Client for Grafana
*
* Comprehensive library for Grafana HTTP API interactions.
* Supports dashboards, data sources, alerting, folders, annotations, and more.
*
* @example
* ```typescript
* import { GrafanaClient, createGrafanaClient } from './GrafanaClient';
*
* const client = createGrafanaClient(); // Uses GRAFANA_URL and GRAFANA_TOKEN env vars
* const dashboards = await client.searchDashboards({ query: 'production' });
* ```
*/
// =============================================================================
// Types & Interfaces
// =============================================================================
export interface GrafanaConfig {
baseUrl: string;
token: string;
orgId?: number;
timeout?: number;
}
export interface GrafanaApiError extends Error {
statusCode: number;
response: Record<string, unknown>;
}
export interface SearchDashboardsParams {
query?: string;
tag?: string;
folderUid?: string;
starred?: boolean;
limit?: number;
page?: number;
}
export interface DashboardSearchResult {
id: number;
uid: string;
title: string;
uri: string;
url: string;
slug: string;
type: string;
tags: string[];
isStarred: boolean;
folderId?: number;
folderUid?: string;
folderTitle?: string;
folderUrl?: string;
sortMeta: number;
}
export interface DashboardMeta {
type: string;
canSave: boolean;
canEdit: boolean;
canAdmin: boolean;
canStar: boolean;
canDelete: boolean;
slug: string;
url: string;
expires: string;
created: string;
updated: string;
updatedBy: string;
createdBy: string;
version: number;
hasAcl: boolean;
isFolder: boolean;
folderId?: number;
folderUid?: string;
folderTitle?: string;
folderUrl?: string;
provisioned: boolean;
provisionedExternalId?: string;
}
export interface DashboardModel {
id?: number | null;
uid?: string | null;
title: string;
tags?: string[];
timezone?: string;
schemaVersion?: number;
version?: number;
refresh?: string;
panels?: Panel[];
templating?: { list: TemplateVariable[] };
annotations?: { list: AnnotationQuery[] };
time?: { from: string; to: string };
timepicker?: Record<string, unknown>;
description?: string;
}
export interface Panel {
id: number;
type: string;
title: string;
gridPos: { h: number; w: number; x: number; y: number };
targets?: Target[];
options?: Record<string, unknown>;
fieldConfig?: Record<string, unknown>;
datasource?: { type: string; uid: string };
[key: string]: unknown;
}
export interface Target {
refId: string;
datasource?: { type: string; uid: string };
expr?: string;
[key: string]: unknown;
}
export interface TemplateVariable {
name: string;
type: string;
datasource?: { type: string; uid: string };
query?: string | Record<string, unknown>;
[key: string]: unknown;
}
export interface AnnotationQuery {
name: string;
datasource: { type: string; uid: string };
enable: boolean;
[key: string]: unknown;
}
export interface DashboardResponse {
meta: DashboardMeta;
dashboard: DashboardModel;
}
export interface SaveDashboardRequest {
dashboard: DashboardModel;
folderUid?: string;
message?: string;
overwrite?: boolean;
}
export interface SaveDashboardResponse {
id: number;
uid: string;
url: string;
status: string;
version: number;
slug: string;
}
export interface DeleteDashboardResponse {
title: string;
message: string;
id: number;
}
export interface DashboardVersion {
id: number;
dashboardId: number;
parentVersion: number;
restoredFrom: number;
version: number;
created: string;
createdBy: string;
message: string;
}
export interface FolderModel {
id: number;
uid: string;
title: string;
url: string;
hasAcl: boolean;
canSave: boolean;
canEdit: boolean;
canAdmin: boolean;
canDelete: boolean;
createdBy: string;
created: string;
updatedBy: string;
updated: string;
version: number;
parentUid?: string;
}
export interface CreateFolderRequest {
title: string;
uid?: string;
parentUid?: string;
}
export interface DataSourceModel {
id: number;
uid: string;
orgId: number;
name: string;
type: string;
typeLogoUrl: string;
access: string;
url: string;
basicAuth: boolean;
isDefault: boolean;
jsonData?: Record<string, unknown>;
readOnly: boolean;
}
export interface AnnotationModel {
id?: number;
dashboardUID?: string;
panelId?: number;
time: number;
timeEnd?: number;
tags?: string[];
text: string;
}
export interface AlertRule {
uid?: string;
title: string;
ruleGroup: string;
folderUID: string;
noDataState?: string;
execErrState?: string;
for?: string;
condition: string;
annotations?: Record<string, string>;
labels?: Record<string, string>;
data: AlertRuleQuery[];
}
export interface AlertRuleQuery {
refId: string;
relativeTimeRange?: { from: number; to: number };
datasourceUid: string;
model: Record<string, unknown>;
}
// =============================================================================
// GrafanaClient Class
// =============================================================================
export class GrafanaClient {
private baseUrl: string;
private token: string;
private orgId?: number;
private timeout: number;
constructor(config: GrafanaConfig) {
this.baseUrl = config.baseUrl.replace(/\/$/, '');
this.token = config.token;
this.orgId = config.orgId;
this.timeout = config.timeout ?? 30000;
}
private async request<T>(
method: string,
endpoint: string,
options?: {
params?: Record<string, string | number | boolean | undefined>;
body?: unknown;
}
): Promise<T> {
const url = new URL(`${this.baseUrl}${endpoint}`);
if (options?.params) {
Object.entries(options.params).forEach(([key, value]) => {
if (value !== undefined) {
url.searchParams.append(key, String(value));
}
});
}
const headers: Record<string, string> = {
Authorization: `Bearer ${this.token}`,
'Content-Type': 'application/json',
Accept: 'application/json',
};
if (this.orgId) {
headers['X-Grafana-Org-Id'] = String(this.orgId);
}
const controller = new AbortController();
const timeoutId = setTimeout(() => controller.abort(), this.timeout);
try {
const response = await fetch(url.toString(), {
method,
headers,
body: options?.body ? JSON.stringify(options.body) : undefined,
signal: controller.signal,
});
clearTimeout(timeoutId);
let data: unknown;
const contentType = response.headers.get('content-type');
if (contentType?.includes('application/json')) {
data = await response.json();
} else {
data = { message: await response.text() };
}
if (!response.ok) {
const error = new Error(
(data as Record<string, unknown>)?.message?.toString() ?? 'Unknown error'
) as GrafanaApiError;
error.statusCode = response.status;
error.response = data as Record<string, unknown>;
throw error;
}
return data as T;
} catch (err) {
clearTimeout(timeoutId);
if (err instanceof Error && err.name === 'AbortError') {
const error = new Error('Request timeout') as GrafanaApiError;
error.statusCode = 408;
error.response = { message: 'Request timeout' };
throw error;
}
throw err;
}
}
// ---------------------------------------------------------------------------
// Health & Info
// ---------------------------------------------------------------------------
async health(): Promise<{ commit: string; database: string; version: string }> {
return this.request('GET', '/api/health');
}
// ---------------------------------------------------------------------------
// Dashboards
// ---------------------------------------------------------------------------
async searchDashboards(params?: SearchDashboardsParams): Promise<DashboardSearchResult[]> {
const queryParams: Record<string, string | number | boolean | undefined> = {
type: 'dash-db',
limit: params?.limit ?? 100,
page: params?.page ?? 1,
};
if (params?.query) queryParams.query = params.query;
if (params?.tag) queryParams.tag = params.tag;
if (params?.folderUid) queryParams.folderUIDs = params.folderUid;
if (params?.starred !== undefined) queryParams.starred = params.starred;
return this.request('GET', '/api/search', { params: queryParams });
}
async getDashboardByUid(uid: string): Promise<DashboardResponse> {
return this.request('GET', `/api/dashboards/uid/${uid}`);
}
async saveDashboard(request: SaveDashboardRequest): Promise<SaveDashboardResponse> {
return this.request('POST', '/api/dashboards/db', { body: request });
}
async deleteDashboard(uid: string): Promise<DeleteDashboardResponse> {
return this.request('DELETE', `/api/dashboards/uid/${uid}`);
}
async getDashboardVersions(uid: string, limit?: number): Promise<DashboardVersion[]> {
const params = limit && limit > 0 ? { limit } : undefined;
return this.request('GET', `/api/dashboards/uid/${uid}/versions`, { params });
}
async restoreDashboardVersion(uid: string, version: number): Promise<SaveDashboardResponse> {
return this.request('POST', `/api/dashboards/uid/${uid}/restore`, {
body: { version },
});
}
async getDashboardPermissions(
uid: string
): Promise<Array<{ role?: string; teamId?: number; userId?: number; permission: number }>> {
return this.request('GET', `/api/dashboards/uid/${uid}/permissions`);
}
async updateDashboardPermissions(
uid: string,
items: Array<{ role?: string; teamId?: number; userId?: number; permission: number }>
): Promise<{ message: string }> {
return this.request('POST', `/api/dashboards/uid/${uid}/permissions`, {
body: { items },
});
}
// ---------------------------------------------------------------------------
// Folders
// ---------------------------------------------------------------------------
async listFolders(limit?: number): Promise<FolderModel[]> {
return this.request('GET', '/api/folders', { params: { limit: limit ?? 1000 } });
}
async getFolder(uid: string): Promise<FolderModel> {
return this.request('GET', `/api/folders/${uid}`);
}
async createFolder(request: CreateFolderRequest): Promise<FolderModel> {
return this.request('POST', '/api/folders', { body: request });
}
async updateFolder(uid: string, title: string, version: number, overwrite?: boolean): Promise<FolderModel> {
return this.request('PUT', `/api/folders/${uid}`, {
body: { title, version, overwrite: overwrite ?? false },
});
}
async deleteFolder(uid: string, forceDeleteRules?: boolean): Promise<{ message: string }> {
return this.request('DELETE', `/api/folders/${uid}`, {
params: { forceDeleteRules: forceDeleteRules ?? false },
});
}
// ---------------------------------------------------------------------------
// Data Sources
// ---------------------------------------------------------------------------
async listDataSources(): Promise<DataSourceModel[]> {
return this.request('GET', '/api/datasources');
}
async getDataSourceByUid(uid: string): Promise<DataSourceModel> {
return this.request('GET', `/api/datasources/uid/${uid}`);
}
async getDataSourceByName(name: string): Promise<DataSourceModel> {
return this.request('GET', `/api/datasources/name/${name}`);
}
async createDataSource(datasource: Partial<DataSourceModel>): Promise<DataSourceModel & { datasource: DataSourceModel }> {
return this.request('POST', '/api/datasources', { body: datasource });
}
async updateDataSource(uid: string, datasource: Partial<DataSourceModel>): Promise<DataSourceModel> {
return this.request('PUT', `/api/datasources/uid/${uid}`, { body: datasource });
}
async deleteDataSource(uid: string): Promise<{ message: string }> {
return this.request('DELETE', `/api/datasources/uid/${uid}`);
}
async healthCheckDataSource(uid: string): Promise<{ status: string; message: string }> {
return this.request('GET', `/api/datasources/uid/${uid}/health`);
}
async queryDataSource(
queries: Array<{ refId: string; datasource: { uid: string }; [key: string]: unknown }>,
from: string = 'now-1h',
to: string = 'now'
): Promise<{ results: Record<string, unknown> }> {
return this.request('POST', '/api/ds/query', { body: { queries, from, to } });
}
// ---------------------------------------------------------------------------
// Annotations
// ---------------------------------------------------------------------------
async queryAnnotations(params?: {
from?: number;
to?: number;
dashboardUid?: string;
panelId?: number;
tags?: string[];
limit?: number;
}): Promise<AnnotationModel[]> {
const queryParams: Record<string, string | number | boolean | undefined> = {
limit: params?.limit ?? 100,
};
if (params?.from) queryParams.from = params.from;
if (params?.to) queryParams.to = params.to;
if (params?.dashboardUid) queryParams.dashboardUID = params.dashboardUid;
if (params?.panelId) queryParams.panelId = params.panelId;
return this.request('GET', '/api/annotations', { params: queryParams });
}
async createAnnotation(annotation: Omit<AnnotationModel, 'id'>): Promise<AnnotationModel & { id: number }> {
return this.request('POST', '/api/annotations', { body: annotation });
}
async updateAnnotation(id: number, annotation: Partial<AnnotationModel>): Promise<{ message: string }> {
return this.request('PUT', `/api/annotations/${id}`, { body: annotation });
}
async deleteAnnotation(id: number): Promise<{ message: string }> {
return this.request('DELETE', `/api/annotations/${id}`);
}
// ---------------------------------------------------------------------------
// Alerting
// ---------------------------------------------------------------------------
async listAlertRules(): Promise<AlertRule[]> {
return this.request('GET', '/api/v1/provisioning/alert-rules');
}
async getAlertRule(uid: string): Promise<AlertRule> {
return this.request('GET', `/api/v1/provisioning/alert-rules/${uid}`);
}
async createAlertRule(rule: AlertRule): Promise<AlertRule> {
return this.request('POST', '/api/v1/provisioning/alert-rules', { body: rule });
}
async updateAlertRule(uid: string, rule: AlertRule): Promise<AlertRule> {
return this.request('PUT', `/api/v1/provisioning/alert-rules/${uid}`, { body: rule });
}
async deleteAlertRule(uid: string): Promise<void> {
return this.request('DELETE', `/api/v1/provisioning/alert-rules/${uid}`);
}
async listContactPoints(): Promise<Array<Record<string, unknown>>> {
return this.request('GET', '/api/v1/provisioning/contact-points');
}
async getNotificationPolicies(): Promise<Record<string, unknown>> {
return this.request('GET', '/api/v1/provisioning/policies');
}
async getActiveAlerts(): Promise<Array<Record<string, unknown>>> {
return this.request('GET', '/api/alertmanager/grafana/api/v2/alerts');
}
// ---------------------------------------------------------------------------
// Users & Teams
// ---------------------------------------------------------------------------
async getCurrentUser(): Promise<Record<string, unknown>> {
return this.request('GET', '/api/user');
}
async searchUsers(params?: { query?: string; perpage?: number; page?: number }): Promise<{ totalCount: number; users: Array<Record<string, unknown>> }> {
return this.request('GET', '/api/users/search', {
params: { query: params?.query, perpage: params?.perpage ?? 100, page: params?.page ?? 1 },
});
}
async searchTeams(params?: { query?: string; perpage?: number; page?: number }): Promise<{ totalCount: number; teams: Array<Record<string, unknown>> }> {
return this.request('GET', '/api/teams/search', {
params: { query: params?.query, perpage: params?.perpage ?? 100, page: params?.page ?? 1 },
});
}
async getTeam(teamId: number): Promise<Record<string, unknown>> {
return this.request('GET', `/api/teams/${teamId}`);
}
async createTeam(name: string, email?: string): Promise<{ teamId: number; message: string }> {
return this.request('POST', '/api/teams', { body: { name, email } });
}
async deleteTeam(teamId: number): Promise<{ message: string }> {
return this.request('DELETE', `/api/teams/${teamId}`);
}
// ---------------------------------------------------------------------------
// Service Accounts
// ---------------------------------------------------------------------------
async searchServiceAccounts(params?: { query?: string; perpage?: number; page?: number }): Promise<{ totalCount: number; serviceAccounts: Array<Record<string, unknown>> }> {
return this.request('GET', '/api/serviceaccounts/search', {
params: { query: params?.query, perpage: params?.perpage ?? 100, page: params?.page ?? 1 },
});
}
async createServiceAccount(name: string, role: 'Viewer' | 'Editor' | 'Admin' = 'Viewer'): Promise<Record<string, unknown>> {
return this.request('POST', '/api/serviceaccounts', { body: { name, role } });
}
async createServiceAccountToken(serviceAccountId: number, name: string, secondsToLive?: number): Promise<{ id: number; name: string; key: string }> {
return this.request('POST', `/api/serviceaccounts/${serviceAccountId}/tokens`, {
body: { name, secondsToLive: secondsToLive ?? 0 },
});
}
// ---------------------------------------------------------------------------
// Organizations
// ---------------------------------------------------------------------------
async getCurrentOrg(): Promise<Record<string, unknown>> {
return this.request('GET', '/api/org');
}
async listOrgs(): Promise<Array<Record<string, unknown>>> {
return this.request('GET', '/api/orgs');
}
async createOrg(name: string): Promise<{ orgId: number; message: string }> {
return this.request('POST', '/api/orgs', { body: { name } });
}
}
// =============================================================================
// Factory function
// =============================================================================
export function createGrafanaClient(config?: Partial<GrafanaConfig>): GrafanaClient {
const baseUrl = config?.baseUrl ?? process.env.GRAFANA_URL;
const token = config?.token ?? process.env.GRAFANA_TOKEN;
if (!baseUrl) throw new Error('GRAFANA_URL environment variable or baseUrl config required');
if (!token) throw new Error('GRAFANA_TOKEN environment variable or token config required');
return new GrafanaClient({
baseUrl,
token,
orgId: config?.orgId ?? (process.env.GRAFANA_ORG_ID ? parseInt(process.env.GRAFANA_ORG_ID) : undefined),
timeout: config?.timeout,
});
}
Dashboard Operations Workflow
Step-by-step workflow for Grafana dashboard operations using the TypeScript tools.
Prerequisites
export GRAFANA_URL="https://grafana.example.com"
export GRAFANA_TOKEN="your-service-account-token"Workflow: List and Search
# List all dashboards
bun run Tools/DashboardCrud.ts list
# Search by name
bun run Tools/DashboardCrud.ts list --query production
# Filter by tag
bun run Tools/DashboardCrud.ts list --tag monitoring
# JSON output for scripting
bun run Tools/DashboardCrud.ts list --jsonWorkflow: Export Dashboard
# Export to default file (uid.json)
bun run Tools/DashboardCrud.ts export abc123
# Export to custom file
bun run Tools/DashboardCrud.ts export abc123 --output my-dashboard.jsonWorkflow: Create Dashboard
# Create dashboard
bun run Tools/DashboardCrud.ts create --file dashboard.json
# Create in specific folder
bun run Tools/DashboardCrud.ts create --file dashboard.json --folder my-folder-uid
# Create with custom title
bun run Tools/DashboardCrud.ts create --file dashboard.json --title "My Dashboard"Workflow: Update Dashboard
# Update dashboard
bun run Tools/DashboardCrud.ts update abc123 --file updated.json
# Update with commit message
bun run Tools/DashboardCrud.ts update abc123 --file updated.json --message "Added new panel"Workflow: Clone Dashboard
# Clone with auto-generated title
bun run Tools/DashboardCrud.ts clone abc123
# Clone with custom title
bun run Tools/DashboardCrud.ts clone abc123 --title "Production Copy"
# Clone to different folder
bun run Tools/DashboardCrud.ts clone abc123 --title "Dev Copy" --folder dev-folder-uidWorkflow: Version Management
# View version history
bun run Tools/DashboardCrud.ts versions abc123
# Restore specific version
bun run Tools/DashboardCrud.ts restore abc123 --version 5Workflow: Delete Dashboard
bun run Tools/DashboardCrud.ts delete abc123Bulk Operations (TypeScript)
Export All Dashboards by Tag
import { createGrafanaClient } from './Tools/GrafanaClient';
const client = createGrafanaClient();
const dashboards = await client.searchDashboards({ tag: 'production' });
for (const dash of dashboards) {
const full = await client.getDashboardByUid(dash.uid);
await Bun.write(`exports/${dash.uid}.json`, JSON.stringify(full.dashboard, null, 2));
console.log(`Exported: ${dash.uid}`);
}Update Tags in Bulk
import { createGrafanaClient } from './Tools/GrafanaClient';
const client = createGrafanaClient();
const dashboards = await client.searchDashboards({ tag: 'old-tag' });
for (const dash of dashboards) {
const full = await client.getDashboardByUid(dash.uid);
full.dashboard.tags = full.dashboard.tags.filter(t => t !== 'old-tag');
full.dashboard.tags.push('new-tag');
await client.saveDashboard({
dashboard: full.dashboard,
folderUid: full.meta.folderUid,
message: 'Updated tags'
});
console.log(`Updated: ${dash.uid}`);
}Error Handling
| Error | Solution |
|---|---|
| 412 Version Conflict | Fetch latest version first, then update |
| 403 Permission Denied | Check service account role (Viewer/Editor/Admin) |
| 404 Not Found | Verify dashboard UID and organization |