All skills
Skillintermediate

Kibana API Reference

Kibana has its own REST API, separate from Elasticsearch. Use `$KIBANA_URL` (not `$ES_URL`).

Claude Code Knowledge Pack7/10/2026

Overview

Kibana API Reference

Kibana has its own REST API, separate from Elasticsearch. Use $KIBANA_URL (not $ES_URL).

Authentication

# Set Kibana URL (typically .kb. domain, vs .es. for Elasticsearch)
KIBANA_URL="https://your-deployment.kb.us-east-1.aws.elastic.cloud"

# All Kibana mutations require kbn-xsrf header
curl -s "$KIBANA_URL/<endpoint>" \\
  -H "Authorization: ApiKey $ES_API_KEY" \\
  -H "kbn-xsrf: true" \\
  -H "Content-Type: application/json" \\
  -d '<json-body>'

Key differences from ES API:

  • kbn-xsrf: true header required on all POST/PUT/DELETE requests
  • Same API key works for both ES and Kibana
  • URL pattern: replace .es. with .kb. from your ES URL

Health Check

curl -s "$KIBANA_URL/api/status" \\
  -H "Authorization: ApiKey $ES_API_KEY" | jq '{name: .name, status: .status.overall.level, version: .version.number}'

Spaces

Kibana spaces scope dashboards, saved objects, and data views. Prefix API paths with /s/{spaceId} for non-default spaces:

# Default space — no prefix
curl -s "$KIBANA_URL/api/data_views" -H "Authorization: ApiKey $ES_API_KEY"

# Custom space
curl -s "$KIBANA_URL/s/my-space/api/data_views" -H "Authorization: ApiKey $ES_API_KEY"

Data Views

Data views (formerly index patterns) tell Kibana which indices to query.

# List data views
curl -s "$KIBANA_URL/api/data_views" \\
  -H "Authorization: ApiKey $ES_API_KEY" | jq '.data_view[] | {id, title}'

# Create data view
curl -s -X POST "$KIBANA_URL/api/data_views/data_view" \\
  -H "Authorization: ApiKey $ES_API_KEY" \\
  -H "kbn-xsrf: true" \\
  -H "Content-Type: application/json" \\
  -d '{
    "data_view": {
      "id": "my-logs-view",
      "title": "logs-*",
      "timeFieldName": "@timestamp"
    },
    "override": true
  }'

# Delete data view
curl -s -X DELETE "$KIBANA_URL/api/data_views/data_view/my-logs-view" \\
  -H "Authorization: ApiKey $ES_API_KEY" \\
  -H "kbn-xsrf: true"

override: true — overwrites if a data view with that ID already exists. Useful for idempotent scripts.

Saved Objects

Saved objects are Kibana's storage model — dashboards, visualizations, data views, searches, etc.

# Get a saved object — NOT available on serverless
curl -s "$KIBANA_URL/api/saved_objects/dashboard/my-dashboard-id" \\
  -H "Authorization: ApiKey $ES_API_KEY" | jq .

# Find saved objects by type — NOT available on serverless
curl -s "$KIBANA_URL/api/saved_objects/_find?type=dashboard&per_page=100" \\
  -H "Authorization: ApiKey $ES_API_KEY" | jq '.saved_objects[] | {id, title: .attributes.title}'

# Bulk create (with overwrite) — NOT available on serverless
curl -s -X POST "$KIBANA_URL/api/saved_objects/_bulk_create?overwrite=true" \\
  -H "Authorization: ApiKey $ES_API_KEY" \\
  -H "kbn-xsrf: true" \\
  -H "Content-Type: application/json" \\
  -d '[
    {
      "type": "dashboard",
      "id": "my-dashboard",
      "attributes": { "title": "My Dashboard" }
    }
  ]'

# Delete saved object — NOT available on serverless
curl -s -X DELETE "$KIBANA_URL/api/saved_objects/dashboard/my-dashboard-id" \\
  -H "Authorization: ApiKey $ES_API_KEY" \\
  -H "kbn-xsrf: true"

Saved object types: dashboard, visualization, lens, search, index-pattern, map, tag.

Serverless Kibana: On Elastic Cloud Serverless, only _import and _export are available for saved objects. The _find, _bulk_create, individual GET, and DELETE endpoints all return 400 Bad Request. Use _import?overwrite=true for create/update operations.

Dashboard Import / Export

# Export dashboard (includes dependencies)
curl -s -X POST "$KIBANA_URL/api/saved_objects/_export" \\
  -H "Authorization: ApiKey $ES_API_KEY" \\
  -H "kbn-xsrf: true" \\
  -H "Content-Type: application/json" \\
  -d '{
    "objects": [{"type": "dashboard", "id": "my-dashboard-id"}],
    "includeReferencesDeep": true
  }' > dashboard-export.ndjson

# Import dashboard (NDJSON format, overwrite existing)
curl -s -X POST "$KIBANA_URL/api/saved_objects/_import?overwrite=true" \\
  -H "Authorization: ApiKey $ES_API_KEY" \\
  -H "kbn-xsrf: true" \\
  --form file=@dashboard-export.ndjson

Import format is newline-delimited JSON (NDJSON) — one saved object per line.

Programmatic Dashboard Creation

For Elastic Cloud Serverless and portability, use by-value dashboards where visualizations are embedded inline (no separate saved objects needed).

Dashboard Structure

{
  "type": "dashboard",
  "id": "my-dashboard",
  "coreMigrationVersion": "8.8.0",
  "typeMigrationVersion": "10.3.0",
  "attributes": {
    "title": "My Dashboard",
    "description": "Dashboard description",
    "timeRestore": true,
    "timeTo": "now",
    "timeFrom": "now-24h",
    "refreshInterval": {"pause": false, "value": 30000},
    "panelsJSON": "[...panels array as JSON string...]",
    "optionsJSON": "{\\"useMargins\\":true,\\"syncColors\\":false}"
  },
  "references": []
}

Critical: coreMigrationVersion and typeMigrationVersion are required for serverless Kibana imports. Without them, the import returns a 500 Internal Server Error. Include them on all saved objects in NDJSON payloads.

Panel Format (By-Value)

Each panel in panelsJSON has grid position and an embedded visualization:

{
  "type": "lens",
  "gridData": {"x": 0, "y": 0, "w": 12, "h": 8, "i": "panel-1"},
  "panelIndex": "panel-1",
  "embeddableConfig": {
    "attributes": {
      "visualizationType": "lnsMetric",
      "title": "Total Events",
      "state": {
        "filters": [],
        "query": {"query": "", "language": "kuery"},
        "datasourceStates": {
          "formBased": {
            "layers": {
              "layer1": {
                "columns": {
                  "col1": {
                    "operationType": "count",
                    "label": "Count",
                    "dataType": "number",
                    "isBucketed": false,
                    "sourceField": "___records___"
                  }
                },
                "columnOrder": ["col1"]
              }
            }
          }
        },
        "visualization": {
          "layerId": "layer1",
          "layerType": "data",
          "metricAccessor": "col1"
        }
      },
      "references": [
        {"type": "index-pattern", "id": "my-data-view", "name": "indexpattern-datasource-layer-layer1"}
      ]
    }
  }
}

Grid uses a 48-column layout. Common panel sizes: metric w:12 h:7, chart w:24 h:14, table w:48 h:12.

Layer properties: Each layer in datasourceStates.formBased.layers should include:

  • "indexPatternId": "data-view-id" — links the layer to its data view
  • "incompleteColumns": {} — required empty object
  • "columns" and "columnOrder" — the column definitions

Lens Visualization Types

TypevisualizationTypeUse Case
MetriclnsMetricSingle number KPI
XY ChartlnsXYLine, bar, area charts
Pie / DonutlnsPieProportion breakdown
Data TablelnsDatatableTabular drill-down

Column Operation Types

OperationDescriptionisBucketedsourceFieldscale
countDocument countfalse"___records___" (required!)"ratio"
unique_countCardinalityfalsefield name"ratio"
sum, avg, min, maxMetric aggregationsfalsefield name"ratio" (see TSDB caveat)
termsTop values groupingtruefield name"ordinal"
date_histogramTime bucketstruefield name"interval"
filtersCustom filter groupstrue"ordinal"

Critical:

  • count columns must include "sourceField": "___records___". Without it, Kibana throws aggValueCount requires the "field" argument.
  • All columns should include "scale" for XY charts. Without it, XY charts may silently render empty despite correct configuration. Use "interval" for date_histogram, "ordinal" for terms/filters, "ratio" for metrics.
  • TSDB gauge metrics (e.g., metrics.system.memory.utilization, metrics.system.cpu.utilization) with time_series_metric: gauge in their mapping cannot be aggregated via avg/sum/min/max in programmatic Lens panels — the chart renders blank with no error. Use count-based panels for TSDB data instead, or view these metrics through Kibana's built-in Infrastructure/Metrics Explorer UI.

Column with Filter

{
  "col1": {
    "operationType": "count",
    "label": "Error Count",
    "dataType": "number",
    "isBucketed": false,
    "filter": {
      "query": "attributes.event.category: \\"user.error\\"",
      "language": "kuery"
    }
  }
}

XY Chart Visualization Config

Critical: XY charts require a layers array wrapper, preferredSeriesType, legend, and fittingFunction at the top level. Without the layers array, charts render empty with no error. Without legend/fittingFunction, charts may also silently fail to render.

{
  "visualization": {
    "preferredSeriesType": "area_stacked",
    "legend": {"isVisible": true, "position": "right"},
    "fittingFunction": "None",
    "layers": [
      {
        "layerId": "layer1",
        "layerType": "data",
        "seriesType": "area_stacked",
        "xAccessor": "col-date",
        "accessors": ["col-count"],
        "splitAccessor": "col-category"
      }
    ]
  }
}

Series types: line, bar, bar_stacked, area, area_stacked, bar_horizontal, bar_horizontal_stacked.

Control Groups (Dashboard Filters)

Add interactive filter controls to dashboards:

{
  "controlGroupInput": {
    "controlStyle": "oneLine",
    "chainingSystem": "HIERARCHICAL",
    "panelsJSON": {
      "panel-filter-1": {
        "order": 0,
        "width": "medium",
        "type": "optionsListControl",
        "explicitInput": {
          "id": "panel-filter-1",
          "fieldName": "attributes.user.id",
          "title": "User",
          "dataViewId": "my-data-view"
        }
      }
    }
  }
}

Deploy Dashboard (Full Example)

# Generate NDJSON with data view + dashboard
# NOTE: coreMigrationVersion + typeMigrationVersion are required for serverless
cat > /tmp/dashboard.ndjson << 'EOF'
{"type":"index-pattern","id":"my-logs","coreMigrationVersion":"8.8.0","attributes":{"title":"logs-*","timeFieldName":"@timestamp"}}
{"type":"dashboard","id":"my-dashboard","coreMigrationVersion":"8.8.0","typeMigrationVersion":"10.3.0","attributes":{"title":"My Dashboard","timeRestore":true,"timeFrom":"now-24h","timeTo":"now","panelsJSON":"[{\\"type\\":\\"lens\\",\\"gridData\\":{\\"x\\":0,\\"y\\":0,\\"w\\":48,\\"h\\":8,\\"i\\":\\"p1\\"},\\"panelIndex\\":\\"p1\\",\\"embeddableConfig\\":{\\"attributes\\":{\\"visualizationType\\":\\"lnsMetric\\",\\"title\\":\\"Total Docs\\",\\"state\\":{\\"filters\\":[],\\"query\\":{\\"query\\":\\"\\",\\"language\\":\\"kuery\\"},\\"datasourceStates\\":{\\"formBased\\":{\\"layers\\":{\\"l1\\":{\\"columns\\":{\\"c1\\":{\\"operationType\\":\\"count\\",\\"label\\":\\"Count\\",\\"dataType\\":\\"number\\",\\"isBucketed\\":false}},\\"columnOrder\\":[\\"c1\\"]}}}},\\"visualization\\":{\\"layerId\\":\\"l1\\",\\"layerType\\":\\"data\\",\\"metricAccessor\\":\\"c1\\"}},\\"references\\":[{\\"type\\":\\"index-pattern\\",\\"id\\":\\"my-logs\\",\\"name\\":\\"indexpattern-datasource-layer-l1\\"}]}}}]"},"references":[]}
EOF

# Import to Kibana
curl -s -X POST "$KIBANA_URL/api/saved_objects/_import?overwrite=true" \\
  -H "Authorization: ApiKey $(printenv ES_API_KEY)" \\
  -H "kbn-xsrf: true" \\
  --form file=@/tmp/dashboard.ndjson | jq .

For complex dashboards with many panels, use a Python script to generate the NDJSON rather than hand-crafting the JSON strings. See the panelsJSON nesting: it's a JSON string containing an array of panel objects, each with deeply nested Lens state. Errors in this structure cause silent 500 errors on import.

Alerting Rules

# List rules
curl -s "$KIBANA_URL/api/alerting/rules/_find?per_page=100" \\
  -H "Authorization: ApiKey $ES_API_KEY" | jq '.data[] | {id, name: .name, enabled: .enabled}'

# Disable a rule
curl -s -X POST "$KIBANA_URL/api/alerting/rule/{rule_id}/_disable" \\
  -H "Authorization: ApiKey $ES_API_KEY" \\
  -H "kbn-xsrf: true"

# Enable a rule
curl -s -X POST "$KIBANA_URL/api/alerting/rule/{rule_id}/_enable" \\
  -H "Authorization: ApiKey $ES_API_KEY" \\
  -H "kbn-xsrf: true"

Tips

  • kbn-xsrf: true is required on every write operation — Kibana returns 400 without it.
  • By-value dashboards embed visualizations inline — no separate saved object IDs to manage. Required for Elastic Cloud Serverless.
  • NDJSON import is the most reliable way to deploy dashboards programmatically.
  • Data view IDs must match between visualization references and actual data views — mismatches cause "field not found" errors.
  • Space prefix /s/{spaceId}/api — omit for the default space.
  • Derive Kibana URL from ES URL — swap .es. for .kb. in the hostname.