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)#

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