Cannot import community dashboards using the API

Hello,

I’m trying to import this community dashboard to Grafana via the API.

According to the documentation, the correct endpoint would be /api/dashboards/db, however when looking at the browser’s devtools while importing the dashboard via the Grafana dashboard I noticed that /api/dashboards/import was being used instead (and this is an undocumented endpoint).

Regardless I’ve tried with both, and so far I haven’t had any success. Here’s what I did:

  • Downloaded the Grafana’s community dashboard json
  • Altered value of “value” to match the datasource uid I wanted to attach to the dashboard:
    "inputs": [ { "name": "DS_PROMETHEUS", "type": "datasource", "pluginId": "prometheus", "value": "this_one_right_here" }
  • Saved the json with the updated datasource value and attempted to POST to both endpoints

On the /api/dashboards/import I receive the following error:
"message": "Dashboard must be set"

On the /api/dashboards/db I receive this one instead:
{ "message": "bad request data", "traceID": "" }

I’ve searched plenty of similar posts but so far I haven’t been able to find anything that matches this exact issue. Do I need to change anything else on the community’s dashboard json so it’s usable as json data in the POST request?

Additional information:

  • I’m running the grafana/grafana:9.5.2 image in a docker container
  • I have been able to perform other API-related requests
  • The dashboards imports successfully and works via regular Grafana dashboard UI import
  • The datasource guid exists and is correct, and have also tried changing to other created test datasources

Thank you.

SOLUTION:

I have been able to come up with a workaround. In order for a community dashboard to be able to be imported via the Grafana api endpoint /api/dashboards/import the json file must first be changed to include additional values at the end. In order to get the correct format of the json, we must first import it normally via the Grafana’s UI and look for the POST request to api/dashboards/import in the devtools. Once found, copy the POST data (which will be the updated dashboard json) and save it to a different file.
Keep in mind this new json will have the guid of the datasource you chose hardcoded. If you need a different datasource than the one chosen via the GUI, then either query the api endpoint for all the datasources and get the guid, or go to the datasource on the Grafana UI and copy it from the URL.
Also Content-Type: application/json and Accept: application/json were necessary for the POST to process correctly.

Here’s the part that I was missing on the dashboard’s json file:

Here is a working curl:

curl 'http://username:password@grafana:3000/api/dashboards/import' \ # An API token also works
--header 'Content-Type: application/json' \ 
--header 'Accept: application/json' \ 
--data @test.json` # path to the updated json dashboard file

And here is the same request using ansible’s uri module:

    - name: Import dashboard to Grafana
      uri:
        url: "http://user:password@{{ server }}:{{ port }}/api/dashboards/import"
        method: "POST"
        force_basic_auth: true
        src: ./path_to_updated_dashboard_json.json
        body_format: json
        headers: 
          Accept: application/json
          Content-Type: application/json
        status_code: 200

Those “community” dashboards are exported from the UI = you can import them via UI. API/UI export produce different outputs, so you can’t mix their outputs.

It is still possible to use API, but it very hackish, undocumented approach. See example in readme GitHub - monitoringartist/grafana-aws-cloudwatch-dashboards: 40+ Grafana dashboards for AWS CloudWatch metrics: EC2, Lambda, S3, ELB, EMR, EBS, SNS, SES, SQS, RDS, EFS, ElastiCache, Billing, API Gateway, VPN, Step Functions, Route 53, CodeBuild, ... - but don’t expect that it will be working for you case - you have input variable, so you will need to deal with it first.

The API documentation states that the endpoint /api/dashboards/db

Creates a new dashboard or updates an existing dashboard.

Unless I’m understanding something incorrectly, shouldn’t it successfully accept a dashboard as a json file via a POST? I can alter the community dashboard as needed, I’m just trying to understand what exactly needs to be changed for the dashboard import to work

You are correct. But import API accepted json format is different then json format produced by UI export.

I have been able to perform an import using postman to /api/dashboards/import. I simply copied the form data of the POST (body) of the request from the devtools when I added the dashboard manually via the Grafana UI.
I have also been able to pinpoint the issue to the content-length header, however I haven’t figured out how do it via curl or wget but I’ll continue to update this thread

Thank you for the help, I’ve posted the solution. I actually based it off of the one in the github link you provided, so thank you :grinning:

I am trying the same via POSTMAN but i am getting bad request or
{“message”:“Dashboard must be set”}

“timepicker”: {},
“timezone”: “”,
“title”: “Polaris test_anuj”,
“version”: 5,
“weekStart”: “”,
“message”: “Made changes to xyz”,
“overwrite”: true
}

I tried both the API :slight_smile:

api/dashboards/import
api/dashboards/db

Hey Max,

I have the json of the dashboards I was able to import via API on my public repository where I used this.

You can find the Ansible playbook that’s performing the POST here

In sum, I figured out I had to perform a change to the dashboard’s json file before being able to send it via POST to the endpoint api/dashboards/import. This change was adding the following to the dashboard’s json (see the entire jsons on the repository’s “dashboards” folder):

    "inputs": [
        {
            "name": "DS_PROMETHEUS",
            "type": "datasource",
            "pluginId": "prometheus",
            "value": "to-change"
        }
    ]

As you can see I have a "value": "to-change" which is a placeholder for my Prometheus datasource UID. Before POSTing the dashboard to Grafana, I have to retrieve the uid of the datasource I want to use and replace the to-change with that uid. Note: The type and plugin would likely also have to change if you’re not using a Prometheus datasource.

Also I believe the headers sent on the request were very much necessary:

    - name: Import dashboard to Grafana
      uri:
        url: "http://{{ grafana_creds }}@{{ grafana_server }}/{{ import_dashboard_api_endpoint }}"
        method: "POST"
        force_basic_auth: true
        src: ./updated_dashboard.json
        body_format: json
        headers: 
          Accept: application/json
          Content-Type: application/json
        status_code: 200

Hope this helps!

Edit: I just read back and noticed I missed mentioning that in order to have a correctly-formatted community json dashboard I first had to manually import it via Grafana UI and capture the POST request done with the browser to retrieve a new, updated json which can be used for sending via API after performing the changes mentioned above.

Hello,

Thank you for reply.

I have my own JSON template and i exported from Grafana GUI dashboard. Now i wanted to import this template via HTTP API to Grafana server .

NOTE,I am using Infinity data source.

Idea is not to create manual dashboard from Grafana GUI.

i am able to get success response from POST man but when i go i grafana dashboard , i can see dashboard is created but empty data and data source.

Template file:

{
“annotations”: {
“list”: [
{
“builtIn”: 1,
“datasource”: {
“type”: “datasource”,
“uid”: “grafana”
},
“enable”: true,
“hide”: true,
“iconColor”: “rgba(0, 211, 255, 1)”,
“name”: “Annotations & Alerts”,
“target”: {
“limit”: 100,
“matchAny”: false,
“tags”: ,
“type”: “dashboard”
},
“type”: “dashboard”
}
]
},
“editable”: true,
“fiscalYearStartMonth”: 0,
“graphTooltip”: 0,
“id”: 287,
“links”: [
{
“asDropdown”: false,
“icon”: “external link”,
“includeVars”: false,
“keepTime”: false,
“tags”: ,
“targetBlank”: false,
“title”: “Poralis dashboard”,
“tooltip”: “Open the poralis dashboard”,
“type”: “link”,
“url”: “https://xxxxxxxxxxx/d/phuoc-test/p-test-infinity?orgId=1&from=1704933710338&to=1704976910338
}
],
“liveNow”: false,
“panels”: [
{
“collapsed”: false,
“datasource”: {
“type”: “prometheus”,
“uid”: “prometheus”
},
“gridPos”: {
“h”: 1,
“w”: 24,
“x”: 0,
“y”: 0
},
“id”: 5,
“panels”: ,
“targets”: [
{
“datasource”: {
“type”: “prometheus”,
“uid”: “prometheus”
},
“refId”: “A”
}
],
“title”: “Results”,
“type”: “row”
},
{
“datasource”: {
“type”: “yesoreyeram-infinity-datasource”,
“uid”: “dba7e1a1-c9f2-4b29-b423-3e11b204f500”
},
“fieldConfig”: {
“defaults”: {
“color”: {
“mode”: “continuous-GrYlRd”
},
“mappings”: ,
“thresholds”: {
“mode”: “absolute”,
“steps”: [
{
“color”: “green”,
“value”: null
},
{
“color”: “red”,
“value”: 80
}
]
}
},
“overrides”:
},
“gridPos”: {
“h”: 9,
“w”: 12,
“x”: 0,
“y”: 1
},
“id”: 10,
“options”: {
“displayMode”: “gradient”,
“minVizHeight”: 10,
“minVizWidth”: 0,
“orientation”: “horizontal”,
“reduceOptions”: {
“calcs”: [
“lastNotNull”
],
“fields”: “/^Number of type$/”,
“values”: true
},
“showUnfilled”: true,
“valueMode”: “color”
},
“pluginVersion”: “10.1.2”,
“targets”: [
{
“columns”: ,
“datasource”: {
“type”: “yesoreyeram-infinity-datasource”,
“uid”: “dba7e1a1-c9f2-4b29-b423-3e11b204f500”
},
“filters”: [
{
“field”: “Number of namespace”,
“operator”: “equals”,
“value”: [
“”
]
}
],
“format”: “table”,
“global_query_id”: “”,
“json_options”: {
“columnar”: false
},
“parser”: “uql”,
“refId”: “A”,
“root_selector”: “Results”,
“source”: “url”,
“type”: “json”,
“uql”: “parse-json\r\n| project "Results"\r\n| summarize "Number of type"=count() by "Kind"”,
“url”: “http://xxxxxxxxxxxxxxx:30013/example-test.json”,
“url_options”: {
“data”: “”,
“method”: “GET”
}
}
],
“title”: “Number of kind”,
“type”: “bargauge”
},
{
“datasource”: {
“type”: “yesoreyeram-infinity-datasource”,
“uid”: “dba7e1a1-c9f2-4b29-b423-3e11b204f500”
},
“fieldConfig”: {
“defaults”: {
“color”: {
“mode”: “continuous-GrYlRd”
},
“custom”: {
“axisCenteredZero”: false,
“axisColorMode”: “text”,
“axisLabel”: “”,
“axisPlacement”: “auto”,
“fillOpacity”: 80,
“gradientMode”: “none”,
“hideFrom”: {
“legend”: false,
“tooltip”: false,
“viz”: false
},
“lineWidth”: 1,
“scaleDistribution”: {
“type”: “linear”
},
“thresholdsStyle”: {
“mode”: “off”
}
},
“mappings”: ,
“thresholds”: {
“mode”: “absolute”,
“steps”: [
{
“color”: “green”,
“value”: null
},
{
“color”: “red”,
“value”: 80
}
]
}
},
“overrides”:
},
“gridPos”: {
“h”: 9,
“w”: 12,
“x”: 12,
“y”: 1
},
“id”: 11,
“options”: {
“barRadius”: 0,
“barWidth”: 0.97,
“colorByField”: “Namespace”,
“fullHighlight”: false,
“groupWidth”: 0.7,
“legend”: {
“calcs”: ,
“displayMode”: “list”,
“placement”: “bottom”,
“showLegend”: true
},
“orientation”: “horizontal”,
“showValue”: “auto”,
“stacking”: “none”,
“tooltip”: {
“mode”: “single”,
“sort”: “none”
},
“xTickLabelRotation”: 0,
“xTickLabelSpacing”: 0
},
“pluginVersion”: “10.1.2”,
“targets”: [
{
“columns”: ,
“datasource”: {
“type”: “yesoreyeram-infinity-datasource”,
“uid”: “dba7e1a1-c9f2-4b29-b423-3e11b204f500”
},
“filters”: [
{
“field”: “Number of namespace”,
“operator”: “equals”,
“value”: [
“”
]
}
],
“format”: “table”,
“global_query_id”: “”,
“json_options”: {
“columnar”: false
},
“parser”: “uql”,
“refId”: “A”,
“root_selector”: “Results”,
“source”: “url”,
“type”: “json”,
“uql”: “parse-json\r\n| project "Results"\r\n| summarize "Recommend by namespace"=count() by "Namespace"”,
“url”: “http://xxxxxxxxxxxxxxxx:30013/example-test.json”,
“url_options”: {
“data”: “”,
“method”: “GET”
}
}
],
“title”: “Recommended by namespace”,
“type”: “barchart”
},
{
“datasource”: {
“type”: “yesoreyeram-infinity-datasource”,
“uid”: “dba7e1a1-c9f2-4b29-b423-3e11b204f500”
},
“fieldConfig”: {
“defaults”: {
“color”: {
“mode”: “thresholds”
},
“custom”: {
“align”: “left”,
“cellOptions”: {
“type”: “color-text”
},
“filterable”: true,
“inspect”: false
},
“mappings”: [
{
“options”: {
“danger”: {
“color”: “red”,
“index”: 0,
“text”: “danger”
},
“warning”: {
“color”: “orange”,
“index”: 1,
“text”: “warning”
}
},
“type”: “value”
}
],
“thresholds”: {
“mode”: “absolute”,
“steps”: [
{
“color”: “text”,
“value”: null
}
]
},
“unit”: “string”
},
“overrides”: [
{
“matcher”: {
“id”: “byName”,
“options”: “kind”
},
“properties”: [
{
“id”: “custom.width”,
“value”: 284
}
]
},
{
“matcher”: {
“id”: “byName”,
“options”: “Kind”
},
“properties”: [
{
“id”: “custom.width”,
“value”: 183
}
]
},
{
“matcher”: {
“id”: “byName”,
“options”: “Name”
},
“properties”: [
{
“id”: “custom.width”,
“value”: 231
}
]
}
]
},
“gridPos”: {
“h”: 18,
“w”: 24,
“x”: 0,
“y”: 10
},
“id”: 3,
“options”: {
“cellHeight”: “sm”,
“footer”: {
“countRows”: false,
“fields”: “”,
“reducer”: [
“sum”
],
“show”: false
},
“showHeader”: true,
“sortBy”: [
{
“desc”: true,
“displayName”: “Severity”
}
]
},
“pluginVersion”: “10.1.2”,
“targets”: [
{
“columns”: [
{
“selector”: “Kind”,
“text”: “Kind of pod”,
“type”: “string”
},
{
“selector”: “Name”,
“text”: “Name”,
“type”: “string”
},
{
“selector”: “Namespace”,
“text”: “Namespace”,
“type”: “string”
},
{
“selector”: “Results”,
“text”: “Results”,
“type”: “string”
}
],
“csv_options”: {
“columns”: “”,
“comment”: “”,
“delimiter”: “,”,
“relax_column_count”: false,
“skip_empty_lines”: false,
“skip_lines_with_error”: false
},
“data”: “”,
“datasource”: {
“type”: “yesoreyeram-infinity-datasource”,
“uid”: “dba7e1a1-c9f2-4b29-b423-3e11b204f500”
},
“filters”: ,
“format”: “table”,
“global_query_id”: “”,
“groq”: “*”,
“json_options”: {
“root_is_not_array”: true
},
“parser”: “uql”,
“refId”: “A”,
“root_selector”: “Results”,
“source”: “url”,
“type”: “json”,
“uql”: “parse-json\r\n| project "Results"\r\n| project-away "CreatedTime"\r\n| extend "Kind", "Name","Namespace", "ID"=strcat("Results.pdbDisruptionsIsZero.ID","Results.sensitiveConfigmapContent.ID","Results.rolebindingClusterAdminClusterRole.ID","Results.rolebindingClusterAdminRole.ID","Results.rolebindingClusterRolePodExecAttach.ID","Results.rolebindingRolePodExecAttach.ID","Results.rolePodExecAttach.ID"),"Message"=strcat("Results.pdbDisruptionsIsZero.Message","Results.sensitiveConfigmapContent.Message","Results.rolebindingClusterAdminClusterRole.Message","Results.rolebindingClusterAdminRole.Message","Results.rolebindingClusterRolePodExecAttach.Message","Results.rolebindingRolePodExecAttach.Message","Results.rolePodExecAttach.Message"), "Severity"=strcat("Results.pdbDisruptionsIsZero.Severity","Results.sensitiveConfigmapContent.Severity", "Results.rolePodExecAttach.Severity"),"Category"=strcat("Results.pdbDisruptionsIsZero.Category","Results.sensitiveConfigmapContent.Category","Results.rolebindingClusterAdminClusterRole.Category","Results.rolebindingClusterAdminRole.Category","Results.rolebindingClusterRolePodExecAttach.Category","Results.rolebindingRolePodExecAttach.Category","Results.rolePodExecAttach.Category"), "Success"=strcat("Results.pdbDisruptionsIsZero.Success","Results.sensitiveConfigmapContent.Success","Results.rolebindingClusterAdminClusterRole.Success","Results.rolebindingClusterAdminRole.Success","Results.rolebindingClusterRolePodExecAttach.Success","Results.rolebindingRolePodExecAttach.Success","Results.rolePodExecAttach.Success")\r\n| project-away "PodResult", "Results"”,
“url”: “http://xxxx:30013/example-test.json”,
“url_options”: {
“data”: “”,
“method”: “GET”
}
}
],
“title”: “Recommendations Details”,
“type”: “table”
}
],
“refresh”: “”,
“schemaVersion”: 38,
“style”: “dark”,
“tags”: ,
“templating”: {
“list”:
},
“time”: {
“from”: “now-5m”,
“to”: “now”
},
“timepicker”: {},
“timezone”: “”,
“title”: “Polaris test”,
“uid”: “c69a76b5-a006-4c27-95de-3dcdb5d69dad”,
“version”: 5,
“weekStart”: “”
}


API: /api/dashboards/import

changes done in POSTMAN :

“dashboard”: {
“title”: “Polaris123”,
“message”: “Made changes to xyz”
},
“timepicker”: {},
“timezone”: “”,
“title”: “Polaris123”,
“uid”: “Polaris123_ID”,
“version”: 2,
“weekStart”: “”,
“overwrite”: true

It looks like you may be missing the inputs part of the json where you specify the datasource to use with the dashboard:

	"inputs": [
		{
			"name": "DS_SIGNCL-PROMETHEUS",
			"type": "datasource",
			"pluginId": "prometheus",
			"value": "your_datasource_uid"
		}
	],

In your case since if you’re using Infinity these fields will be different.

Like I mentioned, it seems that the dashboard json that is exported from Grafana GUI does not include all of the necessary information in order to import it back via API, hence why I mentioned this in the suggested solution

i tried below JSON , its created dashboard but without adding the data source

{
“dashboard”: {
“id”: null,
“uid”: null,
“title”: “pol_anuj1”,
“timezone”: “browser”,
“schemaVersion”: 21,
“version”: 1,
“links”: ,
“panels”: [
{
“id”: null,
“gridPos”: {
“h”: 8,
“w”: 12,
“x”: 0,
“y”: 0
},
“inputs”: [
{
“name”: “Infinity”,
“type”: “yesoreyeram-infinity-datasource”,
“pluginId”: “”,
“value”: “dba7e1a1-c9f2-4b29-b423-3e11b204f500”
}
],
“targets”: [
{
“expr”: “your_metric_query_here”
}
],
“type”: “graph”,
“legend”: {
“show”: true
},
“yaxes”: [
{
“format”: “short”,
“logBase”: 1,
“max”: null,
“min”: null,
“show”: true
},
{
“format”: “short”,
“logBase”: 1,
“max”: null,
“min”: null,
“show”: true
}
],
“xaxis”: {
“show”: true
}
}
// Add more panels as needed
],
“time”: {
“from”: “now-6h”,
“to”: “now”
},
“timepicker”: {
“refresh_intervals”: [
“5s”,
“10s”,
“30s”,
“1m”,
“5m”,
“15m”,
“1h”,
“6h”,
“12h”,
“24h”,
“2d”,
“7d”,
“30d”
],
“time_options”: [
“5m”,
“15m”,
“1h”,
“6h”,
“12h”,
“24h”,
“2d”,
“7d”,
“30d”
]
},
“timezone”: “browser”,
“editable”: true,
“gnetId”: null,
“hideControls”: false,
“sharedCrosshair”: false,
“rows”: ,
“annotations”: ,
“refresh”: “10s”,
“schemaVersion”: 21,
“version”: 8,
“links”: ,
“panels”: ,
“style”: “dark”,
“tags”: ,
“templating”: {
“list”:
}
},
“folderTitle”:“General”,
“folderId”: 0,
“inputs”: ,
“message”: “”
}

Refer to this part of my solution:

In order to get the correct format of the json, we must first import it normally via the Grafana’s UI and look for the POST request to api/dashboards/import in the devtools. Once found, copy the POST data (which will be the updated dashboard json) and save it to a different file

Try and download the json from the browser’s devtools after you import it via GUI, then add the necessary “inputs” part to the json, and attempt to POST via Postman once again.

1 Like