Getting started
Three calls to a working Coach
Build the landing screen, open a conversation, ask a question. Everything on this page after these three is polish.
Before you start
You need an access token for the end customer and a location_id they have access to. The Coach is scoped to a location, not a user. The location_id goes in the path of every call, never in the body or query string. The customer also needs consumption history: with no usable data the landing screen comes back empty and you fall back to chat only.
Tokens, scopes and the host for your environment are covered in the Insights API reference. The Coach uses the same credentials as the rest of Insights, so if you are already calling Insights you have everything you need.
Cards, answers and suggestions come back in the language the location is set to. You can override that per request with language, for example when your app has its own language picker. Currency and number formatting stay with the location's market either way.
01
Build the landing screen
Up to six conversation starters, ranked, already written in the customer's language.
POST https://<your-eliq-host>/v3/locations/4711/energy-coach/spotlights
Authorization: Bearer <access-token>
Content-Type: application/json
{}
Every call takes the same two headers. They are left out of the examples below to keep them short.
{
"customer_name": "Anna",
"coverage": { "coverage_description": "6 months (since November 2025)", … },
"total_candidates": 47,
"spotlights": [
{
"kind": "peak_hour",
"tone": "bad",
"topic": "Your most expensive hour",
"headline": "5–6pm is costing you 340 kr a month.",
"body": "Your usage peaks exactly when electricity is priciest…",
"chat_prompt": "Why are my evenings so expensive?",
"relevance": 82, "impact": 74, "recency": 95, "summary": 81
}
]
}
- Render the cards in the order you receive them. They are already ranked; re-sorting throws away the ranking.
chat_prompt is the seed question. On tap, send it as the customer's first message.
summary is a score, not text. It is the weighted rank, 0.45×impact + 0.30×relevance + 0.25×recency. Never render it.
total_candidates is the breadth of the analysis, not the card count. Good for a "top 6 of 47" line.
coverage.coverage_description is a ready-made string for your "based on N months" disclosure.
The 24 values of kind
capacity_tariff · category_change · cold_heating · daily_recap · ev_charging · fallback · forecast_next_month · month_so_far · monthly_report · peak_hour · peer_gap · price_spread · profile_optional · profile_required · pv_monthly · recent_anomaly · recommendations · solar · solar_good_day · spot_vs_flat · standby · standout_hour · streak · typical_weekday
Map these to icons, and give the map a default. New kinds are added as the Coach learns to spot new things, without a client release.
Empty is not an error
An empty spotlights array means the customer has no eligible data yet. Show the chat on its own.
02
Open a conversation
One id, carried in the path of everything that follows.
POST /v3/locations/4711/energy-coach/conversations
→ 201 { "conversation_id": "8f14e45f-ea0e-4b9c-9f2a-2b1c7d3e4f50" }
Cheap by design: no model call, no database row. The row is written by the first question, so a landing screen nobody talks to leaves nothing behind.
Two conversations can be open at once. Nothing on the server tracks a "current" one, so the id in your path is the only thing that decides which conversation a request belongs to.
Why this is its own call
The id is bound to your client and that location when it is minted, and asking against an id the server does not recognise is a 404. If asking without an id quietly started a new conversation, any client-side null would wipe the customer's history mid-chat, and they would see the Coach forget everything they had just told it.
03
Ask
Prose, follow-up chips, and sometimes a chart.
POST /v3/locations/4711/energy-coach/conversations/{id}/ask
{ "question": "Why was January so expensive?" }
{
"answer": "January was 18% colder than December, and…",
"suggestions": ["What can I do about it?", "How does February look?"],
"widgets": [ … ]
}
- Render
suggestions as chips. They carry most of the engagement in the chat.
- Cap
question at 2000 characters client-side. Over that is a 400, and the customer loses the turn.
- Resuming needs no special handling. Ask against a conversation from months ago and the server rebuilds it from storage. There is no resume call.
Do not auto-retry a timed-out ask
This call is not idempotent. Every request is a fresh turn, so a retry after a timeout costs a second model call and can leave the same question twice in the customer's history, with two different answers. If a request times out, surface it and let the customer decide.
The answer is not streamed
It arrives as one complete JSON body once the model has finished, so the whole turn is a single wait rather than text appearing word by word. Build a loading state into the chat and give the request a generous timeout.
Next
Conversation history
List what the customer has asked before, read one back, continue it, or delete it.
GET /v3/locations/4711/energy-coach/conversations?page_size=20
{
"retention_days": 365,
"items": [{
"conversation_id": "8f14e45f-…",
"title": "Changing my fuse size",
"started_at": "2026-09-14T18:22:31Z",
"last_turn_at": "2026-09-14T18:31:04Z",
"turn_count": 8,
"language_code": "en-GB"
}],
"paging": { "page_size": 20, "continuation_token": "MTc1ODAwMDAwMDAw…" }
}
- Render
retention_days. Do not hardcode "twelve months". It comes from the same setting the deletion job reads, so your copy cannot disagree with what actually happens.
page_size defaults to 20 and caps at 50. Outside 1 to 50 is a 400 rather than a quietly smaller page.
continuation_token is opaque and absent on the last page. Send it back unchanged.
turn_count counts messages, so eight means four exchanges.
- An empty list means one thing only: no conversations yet. Conversations with no turns, and ones the customer deleted, never appear.
Reading one back
GET /v3/locations/4711/energy-coach/conversations/{id}
Returns turns[] oldest first, each with turn_index, role, content, created_at, and feedback when the customer rated it. User turns are even, assistant turns odd. Capped at 500 messages, and empty turns are skipped, so turn_index is not guaranteed contiguous.
Deleting one
DELETE /v3/locations/4711/energy-coach/conversations/{id}
→ 204
Idempotent. Deleting one that is already gone also answers 204, so a retry after a dropped connection does not show the customer an error.