I am using Listonic for groceries etcetera. Would be nice to add it as an integration now we have the todo options. I’ve been out of development some time, so I am not sure if I would be able to build an integration, but if someone is interested like me, the API can be reverse engineered here:
I quote it
Voted! Would be lonely to have it
Is there any public API to Listonic? I mean API to user defined shopping list. Unfortunately, I found nothing.
Also voted. Love listonic
Only the unofficial api used by their web app; Listonic
I created scripts for my AI Voice Assistant to use Listonic directly.
You will have to log in in your browser and extract refresh_token and related info from a network call, and once you do never log out on that machine.
Log into Listonic web app, open F12 console, network. We need to intercept a refresh token, clientauthorization token and DeviceId. You will need to intercept the correct call to /api/loginextended, that only happens after 60 minutes of inactivity so you may have to leave the page open, wait and then add an item. Or use private tab, maybe. The information will be in the header and response sections.
Add this to /config/python_scripts/listonic_get_bearer_token.py
import requests
# === Configuration ===
refresh_token = "YOUR_REFRESH_TOKEN"
url = "https://api.listonic.com/api/loginextended?provider=refresh_token&autoMerge=1&autoDestruct=1"
headers = {
"Content-Type": "application/x-www-form-urlencoded",
"clientauthorization": "Bearer YOUR_CLIENT_AUTHORIZATION",
"version": "web:4.0.0",
"DeviceId": "YOUR_DEVICE_ID"
}
payload = f"refresh_token={refresh_token}"
# === Make the Request ===
response = requests.post(url, headers=headers, data=payload)
# === Parse and Output the Token ===
if response.status_code == 200:
data = response.json()
access_token = data.get("access_token")
print(access_token)
else:
print(f"Error: {response.status_code}")
print(response.text)
And add this to configuration.yaml
shell_command:
get_listonic_token: "python3 /config/python_scripts/listonic_get_bearer_token.py"
This is where the worst part ends, the rest can be done in configuration.yaml directly, we add this:
rest_command:
add_listonic_item:
url: "https://api.listonic.com/api/lists/{{ list_id }}/items"
method: POST
headers:
Authorization: "Bearer {{ token }}"
Content-Type: "application/json"
payload: >
{ "name": "{{ name }}", "Amount": "{{ amount }}", "Unit": "{{ unit }}" }
get_listonic_items:
url: "https://api.listonic.com/api/lists/{{ list_id }}/items"
method: GET
headers:
Authorization: "Bearer {{ token }}"
Content-Type: "application/json"
Now you have the capability to add to a list and pull existing items.
Just to make your life easier, here are my HA scripts that AI can use to seamlessly integrate, you may need to translate the descriptions to your language as the AI reads those and change the mapping between list of shops and shopId (you can get the ID in browser path, easy).
Script to Add an item:
alias: AI Přidat položku na nákupní seznam
description: >-
Přidat položku na nákupní seznam. Pokud něco nevíš použij výchozí hodnoty.
Výchozí obchod je Lidl, výchozí množství je 1. Po přidání položky do seznamu
vždycky stáhni aktuální seznam a zkontroluj, jestli položka na seznamu opravdu je, jinak varuj
uživatele.
sequence:
- variables:
amount_number: >
{% set amount_str = amount | default('1') | trim %} {% set match_list =
amount_str | regex_findall('^([0-9]+)') %} {% if match_list and
match_list[0] %}
{{ match_list[0] }}
{% else %}
{% set parts = amount_str.split() %}
{% if parts | length == 0 %}
1
{% else %}
{% set word = parts[0] | lower %}
{% set phonetic_map = {
'jedna': 1, 'jednu': 1, 'jeden': 1, 'dva': 2, 'dvě': 2,
'tři': 3, 'čtyři': 4, 'pět': 5, 'šest': 6, 'sedm': 7,
'osm': 8, 'devět': 9, 'deset': 10
} %}
{{ phonetic_map.get(word, 1) }}
{% endif %}
{% endif %}
unit: >
{% set parts = (amount | default('') | trim).split() %} {% if parts |
length >= 2 %}
{{ parts[-1] }}
{% else %}
{{''}}
{% endif %}
list_id_map:
Lidl: "18338"
Globus: "16477"
UniHobby: "20590"
Lékárna: "16783"
list_id: |
{{ list_id_map.get(shop.strip(), '18338') }}
- action: shell_command.get_listonic_token
metadata: {}
data: {}
response_variable: listonic
- data:
list_id: "{{ list_id }}"
token: "{{ listonic.stdout }}"
name: "{{ item }}"
amount: "{{ amount_number | default('1') }}"
unit: "{{ unit | default('') }}"
action: rest_command.add_listonic_item
mode: parallel
max: 255
fields:
shop:
selector:
select:
options:
- Lidl
- Globus
- UniHobby
- Lékárna
name: shop
description: Obchod
default: Lidl
required: true
item:
selector:
text: null
name: item
description: Položka
required: true
amount:
selector:
text: {}
name: amount
description: Množství a jednotka (např. "2l", "1", "karton", "2 krabice")
required: true
default: "1"
And a script that allows the AI to pull all unchecked items on a list:
sequence:
- variables:
list_id_map:
Lidl: "18338"
Globus: "16477"
UniHobby: "20590"
Lékárna: "16783"
list_id: |
{{ list_id_map.get(shop.strip(), '18338') }}
- action: shell_command.get_listonic_token
metadata: {}
data: {}
response_variable: listonic
- data:
list_id: "{{ list_id }}"
token: "{{ listonic.stdout }}"
response_variable: listonic_items
action: rest_command.get_listonic_items
- variables:
items_raw: >-
{{ listonic_items.content | selectattr('Checked', 'equalto', 0) | list
}}
items: |-
{% set ns = namespace(result=[]) %} {% for item in items_raw %}
{% set ns.result = ns.result + [{
'Name': item.Name,
'Amount': item.Amount,
'Unit': item.Unit
}] %}
{% endfor %} {{ ns.result }}
result: "{{ {'items': items} }}"
- stop: Vracím nákupní seznam
response_variable: result
alias: AI Položky na nákupním seznamu
description: Stáhne položky na nákupním seznamu pro daný obchod.
mode: parallel
max: 255
fields:
shop:
selector:
select:
options:
- Lidl
- Globus
- UniHobby
- Lékárna
name: shop
description: Obchod
default: Lidl
required: true
You can easily add more functionality using these examples as a base. Just intercept the command you want to use on the web app and add it to configuration.yaml.
Enjoy