REST API#
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 NO5 zone on a particular date
url = "https://www.hvakosterstrommen.no/api/v1/prices/2025/08-21_NO5.json"
response = requests.get(url, verify=False) # Disable SSL verification for testing purposes
print(response.json())
[{'NOK_per_kWh': 0.54988, 'EUR_per_kWh': 0.0461, 'EXR': 11.928, 'time_start': '2025-08-21T00:00:00+02:00', 'time_end': '2025-08-21T01:00:00+02:00'}, {'NOK_per_kWh': 0.63958, 'EUR_per_kWh': 0.05362, 'EXR': 11.928, 'time_start': '2025-08-21T01:00:00+02:00', 'time_end': '2025-08-21T02:00:00+02:00'}, {'NOK_per_kWh': 0.59962, 'EUR_per_kWh': 0.05027, 'EXR': 11.928, 'time_start': '2025-08-21T02:00:00+02:00', 'time_end': '2025-08-21T03:00:00+02:00'}, {'NOK_per_kWh': 0.59783, 'EUR_per_kWh': 0.05012, 'EXR': 11.928, 'time_start': '2025-08-21T03:00:00+02:00', 'time_end': '2025-08-21T04:00:00+02:00'}, {'NOK_per_kWh': 0.56062, 'EUR_per_kWh': 0.047, 'EXR': 11.928, 'time_start': '2025-08-21T04:00:00+02:00', 'time_end': '2025-08-21T05:00:00+02:00'}, {'NOK_per_kWh': 0.57505, 'EUR_per_kWh': 0.04821, 'EXR': 11.928, 'time_start': '2025-08-21T05:00:00+02:00', 'time_end': '2025-08-21T06:00:00+02:00'}, {'NOK_per_kWh': 0.62479, 'EUR_per_kWh': 0.05238, 'EXR': 11.928, 'time_start': '2025-08-21T06:00:00+02:00', 'time_end': '2025-08-21T07:00:00+02:00'}, {'NOK_per_kWh': 0.56074, 'EUR_per_kWh': 0.04701, 'EXR': 11.928, 'time_start': '2025-08-21T07:00:00+02:00', 'time_end': '2025-08-21T08:00:00+02:00'}, {'NOK_per_kWh': 0.47819, 'EUR_per_kWh': 0.04009, 'EXR': 11.928, 'time_start': '2025-08-21T08:00:00+02:00', 'time_end': '2025-08-21T09:00:00+02:00'}, {'NOK_per_kWh': 0.4863, 'EUR_per_kWh': 0.04077, 'EXR': 11.928, 'time_start': '2025-08-21T09:00:00+02:00', 'time_end': '2025-08-21T10:00:00+02:00'}, {'NOK_per_kWh': 0.5141, 'EUR_per_kWh': 0.0431, 'EXR': 11.928, 'time_start': '2025-08-21T10:00:00+02:00', 'time_end': '2025-08-21T11:00:00+02:00'}, {'NOK_per_kWh': 0.58566, 'EUR_per_kWh': 0.0491, 'EXR': 11.928, 'time_start': '2025-08-21T11:00:00+02:00', 'time_end': '2025-08-21T12:00:00+02:00'}, {'NOK_per_kWh': 0.51589, 'EUR_per_kWh': 0.04325, 'EXR': 11.928, 'time_start': '2025-08-21T12:00:00+02:00', 'time_end': '2025-08-21T13:00:00+02:00'}, {'NOK_per_kWh': 0.55632, 'EUR_per_kWh': 0.04664, 'EXR': 11.928, 'time_start': '2025-08-21T13:00:00+02:00', 'time_end': '2025-08-21T14:00:00+02:00'}, {'NOK_per_kWh': 0.59652, 'EUR_per_kWh': 0.05001, 'EXR': 11.928, 'time_start': '2025-08-21T14:00:00+02:00', 'time_end': '2025-08-21T15:00:00+02:00'}, {'NOK_per_kWh': 0.63123, 'EUR_per_kWh': 0.05292, 'EXR': 11.928, 'time_start': '2025-08-21T15:00:00+02:00', 'time_end': '2025-08-21T16:00:00+02:00'}, {'NOK_per_kWh': 0.58674, 'EUR_per_kWh': 0.04919, 'EXR': 11.928, 'time_start': '2025-08-21T16:00:00+02:00', 'time_end': '2025-08-21T17:00:00+02:00'}, {'NOK_per_kWh': 0.59652, 'EUR_per_kWh': 0.05001, 'EXR': 11.928, 'time_start': '2025-08-21T17:00:00+02:00', 'time_end': '2025-08-21T18:00:00+02:00'}, {'NOK_per_kWh': 0.65556, 'EUR_per_kWh': 0.05496, 'EXR': 11.928, 'time_start': '2025-08-21T18:00:00+02:00', 'time_end': '2025-08-21T19:00:00+02:00'}, {'NOK_per_kWh': 0.62097, 'EUR_per_kWh': 0.05206, 'EXR': 11.928, 'time_start': '2025-08-21T19:00:00+02:00', 'time_end': '2025-08-21T20:00:00+02:00'}, {'NOK_per_kWh': 0.62861, 'EUR_per_kWh': 0.0527, 'EXR': 11.928, 'time_start': '2025-08-21T20:00:00+02:00', 'time_end': '2025-08-21T21:00:00+02:00'}, {'NOK_per_kWh': 0.61012, 'EUR_per_kWh': 0.05115, 'EXR': 11.928, 'time_start': '2025-08-21T21:00:00+02:00', 'time_end': '2025-08-21T22:00:00+02:00'}, {'NOK_per_kWh': 0.59926, 'EUR_per_kWh': 0.05024, 'EXR': 11.928, 'time_start': '2025-08-21T22:00:00+02:00', 'time_end': '2025-08-21T23:00:00+02:00'}, {'NOK_per_kWh': 0.57505, 'EUR_per_kWh': 0.04821, 'EXR': 11.928, 'time_start': '2025-08-21T23:00:00+02:00', 'time_end': '2025-08-22T00:00:00+02:00'}]
/Users/kristian/miniforge3/envs/ind320_25/lib/python3.12/site-packages/urllib3/connectionpool.py:1097: 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 ~/miniforge3/envs/ind320_25/lib/python3.12/site-packages/requests/models.py:976, in Response.json(self, **kwargs)
975 try:
--> 976 return complexjson.loads(self.text, **kwargs)
977 except JSONDecodeError as e:
978 # Catch JSON-related errors and raise as requests.JSONDecodeError
979 # This aliases json.JSONDecodeError and simplejson.JSONDecodeError
File ~/miniforge3/envs/ind320_25/lib/python3.12/site-packages/simplejson/__init__.py:514, in loads(s, encoding, cls, object_hook, parse_float, parse_int, parse_constant, object_pairs_hook, use_decimal, allow_nan, **kw)
510 if (cls is None and encoding is None and object_hook is None and
511 parse_int is None and parse_float is None and
512 parse_constant is None and object_pairs_hook is None
513 and not use_decimal and not allow_nan and not kw):
--> 514 return _default_decoder.decode(s)
515 if cls is None:
File ~/miniforge3/envs/ind320_25/lib/python3.12/site-packages/simplejson/decoder.py:386, in JSONDecoder.decode(self, s, _w, _PY3)
385 s = str(s, self.encoding)
--> 386 obj, end = self.raw_decode(s)
387 end = _w(s, end).end()
File ~/miniforge3/envs/ind320_25/lib/python3.12/site-packages/simplejson/decoder.py:416, in JSONDecoder.raw_decode(self, s, idx, _w, _PY3)
415 idx += 3
--> 416 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
2 id = 'test'
3 response = requests.get('http://localhost:8000/api/get/{}'.format(id))
----> 4 print(response.json())
File ~/miniforge3/envs/ind320_25/lib/python3.12/site-packages/requests/models.py:980, in Response.json(self, **kwargs)
976 return complexjson.loads(self.text, **kwargs)
977 except JSONDecodeError as e:
978 # Catch JSON-related errors and raise as requests.JSONDecodeError
979 # This aliases json.JSONDecodeError and simplejson.JSONDecodeError
--> 980 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?
See also
Resources
Flask local HTTP server in Python.
Strømpris API (only in Norwegian)
YouTube: What is a REST API? (9m:11s)
YouTube: REST API Crash Course - Introduction + Full Python API Tutorial (52m:49s)