Migrate to RunxBuild and earn up to $50 in hosting credit on your first deposit.

Calculate your savings
unxBuild

Grafana API: Service Accounts, Dashboards as Code, and the Version Split

Sean

Platform Writer

Aug 27, 2026
8 min read

Grafana’s HTTP API lets you manage dashboards, datasources, folders, and users programmatically. Authenticate with a service account token, not an API key, and be aware that the dashboard endpoints have moved.

Grafana API: Service Accounts, Dashboards as Code, and the Version Split

Most people arrive at this API wanting one of two things: dashboards defined in a repository instead of clicked together, or datasources provisioned automatically when an environment is created. Both are well supported. The obstacles are authentication and an API surface that is currently mid-migration.

Table of contents

Authentication: service accounts

Legacy API keys are deprecated. Service accounts replaced them and are better in the ways that matter operationally: they are not tied to a person, they carry their own role, they can hold multiple tokens, and revoking one token does not break everything else.

  1. Go to Administration, then Users and access, then Service accounts.
  2. Create a service account and give it a role - Viewer for read-only automation, Editor for creating dashboards, Admin only if it genuinely needs to manage users.
  3. Add a service account token and copy it. It is shown once.
  4. Use it as a bearer token.
export GRAFANA_URL=https://grafana.example.com
export GRAFANA_TOKEN=glsa_xxxxxxxxxxxx

curl -H "Authorization: Bearer $GRAFANA_TOKEN" \
     -H "Content-Type: application/json" \
     "$GRAFANA_URL/api/org"

Scope the role to what the automation actually does. A CI job that publishes dashboards needs Editor, not Admin, and the difference matters the day the token leaks.

Basic authentication also works for some endpoints, and a handful of administrative endpoints accept only basic auth with an actual admin user. Use tokens everywhere you can and treat basic auth as the exception rather than the pattern.

The endpoint split

Grafana is migrating its API to a resource-oriented structure modelled on Kubernetes conventions. Dashboards are among the first resources to move, which means two shapes exist in the wild and documentation you find may describe either.

The newer form uses a namespaced path with metadata and spec objects, mirroring how Kubernetes resources are structured. The legacy form uses simpler paths under the base API path and is what most existing scripts and tutorials use.

Practically: legacy endpoints still work and are what you will find in most examples. If you are writing something new and long-lived, check which shape your Grafana version serves before committing, because a script written against one will not work against the other.

# Check your version first - it determines which shape applies
curl -H "Authorization: Bearer $GRAFANA_TOKEN" \
     "$GRAFANA_URL/api/health"
# {"database":"ok","version":"11.3.0"}

The rest of the API - datasources, folders, users, teams, annotations, alerting - is not affected in the same way, so most automation is unaffected by the split.

Dashboards as code

The workflow that makes this API worth using: build a dashboard in the interface, export its JSON, commit it, and push it from CI.

  1. Create the dashboard by hand until it is right. Hand-writing dashboard JSON from scratch is a poor use of an afternoon.
  2. Export the JSON via the share menu or the API.
  3. Commit it to a repository.
  4. Push it from CI on merge.
# Fetch an existing dashboard by UID
curl -H "Authorization: Bearer $GRAFANA_TOKEN" \
     "$GRAFANA_URL/api/dashboards/uid/abc123" \
     | jq '.dashboard' > dashboards/api-overview.json
# Push it back - wrap it and set overwrite
jq '{dashboard: ., overwrite: true, folderUid: "prod", message: "ci deploy"}' \
  dashboards/api-overview.json \
  | curl -X POST \
      -H "Authorization: Bearer $GRAFANA_TOKEN" \
      -H "Content-Type: application/json" \
      -d @- \
      "$GRAFANA_URL/api/dashboards/db"

Three details that decide whether this works reliably. Strip the numeric id before pushing, or set it to null - it refers to a database row on the source instance and will not match on the destination. Set overwrite to true or a second push fails with a version conflict. And keep the UID stable, because it is what makes the dashboard the same dashboard across instances.

Datasource UIDs are the other portability problem. A dashboard exported from staging references staging’s datasource UIDs, which do not exist in production. Either use dashboard variables for the datasource, or substitute the UIDs during deployment.

sed "s/\${DS_PROMETHEUS}/$PROD_DS_UID/g" dashboard.json > deploy.json

Other useful endpoints

Beyond dashboards, four areas come up regularly in automation.

# Provision a datasource
curl -X POST -H "Authorization: Bearer $GRAFANA_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"name":"prod-prometheus","type":"prometheus",
       "url":"http://prometheus:9090","access":"proxy",
       "isDefault":true}' \
  "$GRAFANA_URL/api/datasources"

# Create a folder
curl -X POST -H "Authorization: Bearer $GRAFANA_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"uid":"prod","title":"Production"}' \
  "$GRAFANA_URL/api/folders"

# Annotate a deploy - genuinely useful
curl -X POST -H "Authorization: Bearer $GRAFANA_TOKEN" \
  -H "Content-Type: application/json" \
  -d "{\"text\":\"Deployed $VERSION\",\"tags\":[\"deploy\"]}" \
  "$GRAFANA_URL/api/annotations"

That last one is the highest-value, lowest-effort thing on this list. Post an annotation from your deploy pipeline and every dashboard gains a vertical line at each release. When latency changes at 14:32 and there is a deploy marker at 14:31, you have your answer in one glance instead of correlating two systems by hand.

Also worth knowing: file-based provisioning exists as an alternative to the API for datasources and dashboards. YAML files in a provisioning directory are read at startup and are often simpler than API calls for infrastructure that is defined declaratively anyway. The API is the better fit when changes happen at deploy time rather than at instance-build time.

Practical cautions

  • Version differences are real. Endpoints and payload shapes change between major versions. Pin your Grafana version in automation and test after upgrades rather than assuming compatibility.
  • Rate limits. Bulk operations against a hosted instance can hit limits. Batch and add backoff rather than firing hundreds of parallel requests.
  • Token storage. Service account tokens are credentials. Keep them in your platform’s secret storage, not in a repository or a compose file.
  • Provisioned resources are read-only in the interface. Anything created by file provisioning cannot be edited in the UI, which is usually the point but surprises people who then cannot work out why the save button is disabled.
  • Deleting is not undoable. There is no trash for dashboards deleted via the API. Keep the JSON in version control and this stops mattering.

The general principle worth applying: treat the Grafana instance as a rendering target rather than a source of truth. The dashboards live in a repository, the API puts them into Grafana, and rebuilding the instance from scratch is a pipeline run rather than a recovery project.

How this fits the rest of the stack

Deploy annotations only mean something if you know precisely when a deploy happened, which is a property of the deployment platform rather than the dashboard. On RunxBuild every deploy is recorded with its build log and runtime logs, and rollback to the previous version is a button. The RunxBuild hosting calculator shows the service and its managed database as separate line items, so the system you are graphing has a known cost.

Useful related references:

FAQ

How do I authenticate with the Grafana API?

Create a service account under Administration, add a token to it, and send that token as a bearer token in the Authorization header. Legacy API keys are deprecated - service accounts are not tied to a person, carry their own role, and support multiple revocable tokens.

How do I create a Grafana dashboard via the API?

Post the dashboard JSON wrapped in an object with the dashboard, an overwrite flag, and optionally a folder UID. Build the dashboard in the interface first and export its JSON rather than hand-writing it - and strip the numeric id before pushing, since it refers to a row on the source instance.

Why does my dashboard import fail with a version conflict?

Because overwrite was not set to true, or the numeric id from the source instance was left in the payload. Set overwrite explicitly and remove or null the id. Keep the UID stable so the dashboard is recognised as the same dashboard across instances.

What is the best use of the Grafana API in a deploy pipeline?

Posting an annotation on each release. Every dashboard then shows a vertical marker at each deploy, so a latency change one minute after a release is immediately attributable instead of requiring you to correlate two systems by hand. It is a single request.

Should I use the API or file provisioning?

File provisioning suits resources defined declaratively at instance-build time, such as datasources, and makes them read-only in the interface. The API suits changes that happen at deploy time, such as publishing an updated dashboard from CI. Many setups use both.

#grafana api#grafana http api#dashboards as code#service accounts#observability