REST stands for Representational State Transfer.
Architectural style for designing web services.
REST services are stateless, which means that they do not maintain any state between requests. This makes them scalable and reliable.
For us they are mainly interfaces for information retrieval.
Accessing REST¶
REST services are based on the HTTP protocol, and they use a set of well-defined verbs to manipulate resources.
Some are open and free, some need an API key (free or subscription)
The four main verbs in REST are:
GET: Retrieve a resource.
POST: Create a resource.
PUT: Update a resource.
DELETE: Delete a resource.
Retrieving data (GET)¶
A simple example without an API key (Strømpris API):
import requests
# Power prices in the NO1 zone on a particular date
url = "https://www.hvakosterstrommen.no/api/v1/prices/2025/09-29_NO1.json"
response = requests.get(url, verify=False) # Disable SSL verification for testing purposes
print(response.json())[{'NOK_per_kWh': 0.53166, 'EUR_per_kWh': 0.0454, 'EXR': 11.7105, 'time_start': '2025-09-29T00:00:00+02:00', 'time_end': '2025-09-29T01:00:00+02:00'}, {'NOK_per_kWh': 0.52334, 'EUR_per_kWh': 0.04469, 'EXR': 11.7105, 'time_start': '2025-09-29T01:00:00+02:00', 'time_end': '2025-09-29T02:00:00+02:00'}, {'NOK_per_kWh': 0.50589, 'EUR_per_kWh': 0.0432, 'EXR': 11.7105, 'time_start': '2025-09-29T02:00:00+02:00', 'time_end': '2025-09-29T03:00:00+02:00'}, {'NOK_per_kWh': 0.51924, 'EUR_per_kWh': 0.04434, 'EXR': 11.7105, 'time_start': '2025-09-29T03:00:00+02:00', 'time_end': '2025-09-29T04:00:00+02:00'}, {'NOK_per_kWh': 0.52943, 'EUR_per_kWh': 0.04521, 'EXR': 11.7105, 'time_start': '2025-09-29T04:00:00+02:00', 'time_end': '2025-09-29T05:00:00+02:00'}, {'NOK_per_kWh': 0.49828, 'EUR_per_kWh': 0.04255, 'EXR': 11.7105, 'time_start': '2025-09-29T05:00:00+02:00', 'time_end': '2025-09-29T06:00:00+02:00'}, {'NOK_per_kWh': 0.43762, 'EUR_per_kWh': 0.03737, 'EXR': 11.7105, 'time_start': '2025-09-29T06:00:00+02:00', 'time_end': '2025-09-29T07:00:00+02:00'}, {'NOK_per_kWh': 0.78531, 'EUR_per_kWh': 0.06706, 'EXR': 11.7105, 'time_start': '2025-09-29T07:00:00+02:00', 'time_end': '2025-09-29T08:00:00+02:00'}, {'NOK_per_kWh': 0.78765, 'EUR_per_kWh': 0.06726, 'EXR': 11.7105, 'time_start': '2025-09-29T08:00:00+02:00', 'time_end': '2025-09-29T09:00:00+02:00'}, {'NOK_per_kWh': 0.75111, 'EUR_per_kWh': 0.06414, 'EXR': 11.7105, 'time_start': '2025-09-29T09:00:00+02:00', 'time_end': '2025-09-29T10:00:00+02:00'}, {'NOK_per_kWh': 0.74221, 'EUR_per_kWh': 0.06338, 'EXR': 11.7105, 'time_start': '2025-09-29T10:00:00+02:00', 'time_end': '2025-09-29T11:00:00+02:00'}, {'NOK_per_kWh': 0.7469, 'EUR_per_kWh': 0.06378, 'EXR': 11.7105, 'time_start': '2025-09-29T11:00:00+02:00', 'time_end': '2025-09-29T12:00:00+02:00'}, {'NOK_per_kWh': 0.73132, 'EUR_per_kWh': 0.06245, 'EXR': 11.7105, 'time_start': '2025-09-29T12:00:00+02:00', 'time_end': '2025-09-29T13:00:00+02:00'}, {'NOK_per_kWh': 0.72371, 'EUR_per_kWh': 0.0618, 'EXR': 11.7105, 'time_start': '2025-09-29T13:00:00+02:00', 'time_end': '2025-09-29T14:00:00+02:00'}, {'NOK_per_kWh': 0.73319, 'EUR_per_kWh': 0.06261, 'EXR': 11.7105, 'time_start': '2025-09-29T14:00:00+02:00', 'time_end': '2025-09-29T15:00:00+02:00'}, {'NOK_per_kWh': 0.74982, 'EUR_per_kWh': 0.06403, 'EXR': 11.7105, 'time_start': '2025-09-29T15:00:00+02:00', 'time_end': '2025-09-29T16:00:00+02:00'}, {'NOK_per_kWh': 0.79573, 'EUR_per_kWh': 0.06795, 'EXR': 11.7105, 'time_start': '2025-09-29T16:00:00+02:00', 'time_end': '2025-09-29T17:00:00+02:00'}, {'NOK_per_kWh': 0.84304, 'EUR_per_kWh': 0.07199, 'EXR': 11.7105, 'time_start': '2025-09-29T17:00:00+02:00', 'time_end': '2025-09-29T18:00:00+02:00'}, {'NOK_per_kWh': 1.26825, 'EUR_per_kWh': 0.1083, 'EXR': 11.7105, 'time_start': '2025-09-29T18:00:00+02:00', 'time_end': '2025-09-29T19:00:00+02:00'}, {'NOK_per_kWh': 1.45292, 'EUR_per_kWh': 0.12407, 'EXR': 11.7105, 'time_start': '2025-09-29T19:00:00+02:00', 'time_end': '2025-09-29T20:00:00+02:00'}, {'NOK_per_kWh': 0.7517, 'EUR_per_kWh': 0.06419, 'EXR': 11.7105, 'time_start': '2025-09-29T20:00:00+02:00', 'time_end': '2025-09-29T21:00:00+02:00'}, {'NOK_per_kWh': 0.60696, 'EUR_per_kWh': 0.05183, 'EXR': 11.7105, 'time_start': '2025-09-29T21:00:00+02:00', 'time_end': '2025-09-29T22:00:00+02:00'}, {'NOK_per_kWh': 0.50765, 'EUR_per_kWh': 0.04335, 'EXR': 11.7105, 'time_start': '2025-09-29T22:00:00+02:00', 'time_end': '2025-09-29T23:00:00+02:00'}, {'NOK_per_kWh': 0.62358, 'EUR_per_kWh': 0.05325, 'EXR': 11.7105, 'time_start': '2025-09-29T23:00:00+02:00', 'time_end': '2025-09-30T00:00:00+02:00'}]
/Users/kristian/Documents/GitHub/IND320/.venv/lib/python3.12/site-packages/urllib3/connectionpool.py:1110: InsecureRequestWarning: Unverified HTTPS request is being made to host 'www.hvakosterstrommen.no'. Adding certificate verification is strongly advised. See: https://urllib3.readthedocs.io/en/latest/advanced-usage.html#tls-warnings
warnings.warn(
# Write JSON to file for viewing
import json
with open('downloads/power_price.json', 'w') as f:
json.dump(response.json(), f, indent=4)Creating data (POST)¶
For this example we assume there is an API server running locally.
We rely on the Flask framework (see flask_API.py) in the current folder.
Simple POST, GET, “UPDATE”, DELETE
Running a Flask instance from the terminal can be done similar to this:
conda activate tf_M1
python /Users/kristian/Documents/GitHub/IND320/D2Dbook/3_Data_sources/3_APIs/flask_API.py# POST data (stored locally in a dictionary)
id = 'test'
data = {'key': 'value'}
response = requests.post('http://localhost:8000/api/post/{}'.format(id), json=data)
print(response.json()){'key': 'value'}
# GET data
id = 'test'
response = requests.get('http://localhost:8000/api/get/{}'.format(id))
print(response.json())
# You can also test this in a browser since this is an HTTP based (and we use no passwords here).{'key': 'value'}
Change data (UPDATE)¶
This command can mostly be exchanged with POST.
The Flask framework does not have a separate UPDATE (see implementation in flask_API.py).
# UPDATE data
id = 'test'
data = {'key': 'new value'}
response = requests.post('http://localhost:8000/api/update/{}'.format(id), json=data)
print(response.json()){'key': 'new value'}
Remove data (DELETE)¶
Let’s remove some data and try to read it again.
# DELETE data
id = 'test'
response = requests.delete('http://localhost:8000/api/delete/{}'.format(id))
print(response.json()){'key': 'new value'}
# GET data again
id = 'test'
response = requests.get('http://localhost:8000/api/get/{}'.format(id))
print(response.json())---------------------------------------------------------------------------
JSONDecodeError Traceback (most recent call last)
File ~/Documents/GitHub/IND320/.venv/lib/python3.12/site-packages/requests/models.py:1116, in Response.json(self, **kwargs)
1115 try:
-> 1116 return complexjson.loads(self.text, **kwargs)
1117 except JSONDecodeError as e:
1118 # Catch JSON-related errors and raise as requests.JSONDecodeError
1119 # This aliases json.JSONDecodeError and simplejson.JSONDecodeError
File ~/Documents/GitHub/IND320/.venv/lib/python3.12/site-packages/simplejson/__init__.py:517, in loads(s, encoding, cls, object_hook, parse_float, parse_int, parse_constant, object_pairs_hook, use_decimal, allow_nan, array_hook, **kw)
512 if (cls is None and encoding is None and object_hook is None and
513 parse_int is None and parse_float is None and
514 parse_constant is None and object_pairs_hook is None
515 and array_hook is None
516 and not use_decimal and not allow_nan and not kw):
--> 517 return _default_decoder.decode(s)
518 if cls is None:
File ~/Documents/GitHub/IND320/.venv/lib/python3.12/site-packages/simplejson/decoder.py:396, in JSONDecoder.decode(self, s, _w, _PY3)
395 s = str(s, self.encoding)
--> 396 obj, end = self.raw_decode(s)
397 end = _w(s, end).end()
File ~/Documents/GitHub/IND320/.venv/lib/python3.12/site-packages/simplejson/decoder.py:426, in JSONDecoder.raw_decode(self, s, idx, _w, _PY3)
425 idx += 3
--> 426 return self.scan_once(s, idx=_w(s, idx).end())
JSONDecodeError: Expecting value: line 1 column 1 (char 0)
During handling of the above exception, another exception occurred:
JSONDecodeError Traceback (most recent call last)
Cell In[7], line 4
1 # GET data again
2 id = 'test'
3 response = requests.get('http://localhost:8000/api/get/{}'.format(id))
----> 4 print(response.json())
File ~/Documents/GitHub/IND320/.venv/lib/python3.12/site-packages/requests/models.py:1120, in Response.json(self, **kwargs)
1116 return complexjson.loads(self.text, **kwargs)
1117 except JSONDecodeError as e:
1118 # Catch JSON-related errors and raise as requests.JSONDecodeError
1119 # This aliases json.JSONDecodeError and simplejson.JSONDecodeError
-> 1120 raise RequestsJSONDecodeError(e.msg, e.doc, e.pos)
JSONDecodeError: Expecting value: line 1 column 1 (char 0)Exercise¶
Look at flask_API.py.
Add “try-except” to GET to return an empty JSON when an ‘id’ does not exist.
Are there other potential sources of error here?