401 Client Error when scripting to calculate multiple ndvi indices

Hi all,

I am writing a script that calculates the NDVI index and downloads the image for several dates included in a range using the Sentinel Hub Process API. To do this I am using this documentation.

The problem is that when I run the script it returns the following message:

`401 Client Error: Unauthorized for url: https://services.sentinel-hub.com/api/v1/process

Server response: “{“status”: 401, “reason”: “Unauthorized”, “message”: “You are not authorized! Please provide a valid access token within the header [Authorization: Bearer ] of your request.”, “code”: “COMMON_UNAUTHORIZED”}”`.

Does anyone have any idea what could be happening?

This is my code:

import datetime
import numpy as np
import matplotlib.pyplot as plt

from sentinelhub import (
    SHConfig,
    DataCollection,
    SentinelHubRequest,
    MosaickingOrder,
    SentinelHubDownloadClient,
    BBox,
    bbox_to_dimensions,
    CRS,
    MimeType,
)

from utils import plot_image

# Se carga el perfil creado en archivo /Users/raul/.config/sentinelhub/config.toml usando las credenciales
# creadas en la cuenta de Copernicus (https://shapps.dataspace.copernicus.eu/dashboard/#/account/settings)
# Si no se ha creado antes hay que ejecutar estas líneas de aquí para hacerlo desde este código
# config = SHConfig()
# config.sh_client_id = getpass.getpass("Enter your SentinelHub client id ")
# config.sh_client_secret = getpass.getpass("Enter your SentinelHub client secret ")
# config.sh_token_url = "https://identity.dataspace.copernicus.eu/auth/realms/CDSE/protocol/openid-connect/token"
# config.sh_base_url = "https://sh.dataspace.copernicus.eu"
# config.save("ndvi")

config = SHConfig('ndvi')

# Se crea una área de interés para buscar imágenes. Se pueden obtener fácil desde http://bboxfinder.com/
aoi_coords_wgs84 = [-2.838272, 36.837645, -2.818874, 36.848532]

# Se especifica la resolución de la imagen (m) y se inicializa el bounding box (BBox)
resolution = 10
aoi_bbox = BBox(bbox=aoi_coords_wgs84, crs=CRS.WGS84)
aoi_size = bbox_to_dimensions(aoi_bbox, resolution=resolution)

start = datetime.datetime(2024, 1, 1)
end = datetime.datetime(2024, 3, 20)
n_chunks = 10
tdelta = (end - start) / n_chunks
edges = [(start + i * tdelta).date().isoformat() for i in range(n_chunks)]
slots = [(edges[i], edges[i + 1]) for i in range(len(edges) - 1)]

print('Ventanas de tiempo:')
for slot in slots:
    print(slot)

# Se calcula el NDVI y se hace la petición del resultado
# Documentación y leyenda: https://custom-scripts.sentinel-hub.com/sentinel-2/ndvi/#color-legend
evalscript_ndvi = """
//VERSION=3
function setup() {
   return {
      input: ["B04", "B08", "dataMask"],
      output: { bands: 4 }
   };
}

const ramp = [
   [-0.5, 0x0c0c0c],
   [-0.2, 0xbfbfbf],
   [-0.1, 0xdbdbdb],
   [0, 0xeaeaea],
   [0.025, 0xfff9cc],
   [0.05, 0xede8b5],
   [0.075, 0xddd89b],
   [0.1, 0xccc682],
   [0.125, 0xbcb76b],
   [0.15, 0xafc160],
   [0.175, 0xa3cc59],
   [0.2, 0x91bf51],
   [0.25, 0x7fb247],
   [0.3, 0x70a33f],
   [0.35, 0x609635],
   [0.4, 0x4f892d],
   [0.45, 0x3f7c23],
   [0.5, 0x306d1c],
   [0.55, 0x216011],
   [0.6, 0x0f540a],
   [1, 0x004400],
];

const visualizer = new ColorRampVisualizer(ramp);

function evaluatePixel(samples) {
   let ndvi = index(samples.B08, samples.B04);
   let imgVals = visualizer.process(ndvi);
   return imgVals.concat(samples.dataMask)
}
"""


def get_ndvi(time_interval):
    return SentinelHubRequest(
        evalscript=evalscript_ndvi,
        input_data=[
            SentinelHubRequest.input_data(
                data_collection=DataCollection.SENTINEL2_L2A,
                time_interval=time_interval,
                mosaicking_order=MosaickingOrder.LEAST_CC,
            )
        ],
        responses=[SentinelHubRequest.output_response("default", MimeType.TIFF)],
        bbox=aoi_bbox,
        size=aoi_size,
        config=config,
    )


# Se crea una lista de peticiones
list_of_requests = [get_ndvi(slot) for slot in slots]
list_of_requests = [request.download_list[0] for request in list_of_requests]

# Se configura la descarga de datos en varios hilos
data = SentinelHubDownloadClient(config=config).download(list_of_requests, max_threads=5)

# Se pueden mostrar las imágenes descargadas
ncols = 4
nrows = 3
aspect_ratio = aoi_size[0] / aoi_size[1]
subplot_kw = {"xticks": [], "frame_on": False}

fig, axs = plt.subplots(ncols=ncols, nrows=nrows, figsize=(5 * ncols * aspect_ratio, 5 * nrows), subplot_kw=subplot_kw)

for idx, image in enumerate(data):
    ax = axs[idx // ncols][idx % ncols]
    ax.imshow(np.clip(image * 2.5 / 255, 0, 1))
    ax.set_title(f"{slots[idx][0]} - {slots[idx][1]}", fontsize=10)

plt.tight_layout()

And this is the detailed error:

Traceback (most recent call last):
  File "/Users/raul/Documents/venvPython/.venv/lib/python3.12/site-packages/sentinelhub/download/handlers.py", line 40, in new_download_func
    return download_func(self, request)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/Users/raul/Documents/venvPython/.venv/lib/python3.12/site-packages/sentinelhub/download/sentinelhub_client.py", line 95, in _execute_download
    response.raise_for_status()
  File "/Users/raul/Documents/venvPython/.venv/lib/python3.12/site-packages/requests/models.py", line 1021, in raise_for_status
    raise HTTPError(http_error_msg, response=self)
requests.exceptions.HTTPError: 401 Client Error: Unauthorized for url: https://services.sentinel-hub.com/api/v1/process

The above exception was the direct cause of the following exception:

Traceback (most recent call last):
  File "/Users/raul/Documents/descargaSentinel/descargaSentinel_v3.1_API.py", line 128, in <module>
    data = SentinelHubDownloadClient(config=config).download(list_of_requests, max_threads=5)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/Users/raul/Documents/venvPython/.venv/lib/python3.12/site-packages/sentinelhub/download/sentinelhub_client.py", line 68, in download
    return super().download(*args, **kwargs)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/Users/raul/Documents/venvPython/.venv/lib/python3.12/site-packages/sentinelhub/download/client.py", line 103, in download
    raise download_exception
  File "/Users/raul/Documents/venvPython/.venv/lib/python3.12/site-packages/sentinelhub/download/client.py", line 100, in download
    results[future_order[future]] = future.result()
                                    ^^^^^^^^^^^^^^^
  File "/Library/Frameworks/Python.framework/Versions/3.12/lib/python3.12/concurrent/futures/_base.py", line 449, in result
    return self.__get_result()
           ^^^^^^^^^^^^^^^^^^^
  File "/Library/Frameworks/Python.framework/Versions/3.12/lib/python3.12/concurrent/futures/_base.py", line 401, in __get_result
    raise self._exception
  File "/Library/Frameworks/Python.framework/Versions/3.12/lib/python3.12/concurrent/futures/thread.py", line 58, in run
    result = self.fn(*self.args, **self.kwargs)
             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/Users/raul/Documents/venvPython/.venv/lib/python3.12/site-packages/sentinelhub/download/client.py", line 116, in _single_download_decoded
    response = self._single_download(request)
               ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/Users/raul/Documents/venvPython/.venv/lib/python3.12/site-packages/sentinelhub/download/client.py", line 129, in _single_download
    response = self._execute_download(request)
               ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/Users/raul/Documents/venvPython/.venv/lib/python3.12/site-packages/sentinelhub/download/handlers.py", line 67, in new_download_func
    return download_func(self, request)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/Users/raul/Documents/venvPython/.venv/lib/python3.12/site-packages/sentinelhub/download/handlers.py", line 46, in new_download_func
    raise DownloadFailedException(
sentinelhub.exceptions.DownloadFailedException: Failed to download from:
https://services.sentinel-hub.com/api/v1/process
with HTTPError:
401 Client Error: Unauthorized for url: https://services.sentinel-hub.com/api/v1/process
Server response: "{"status": 401, "reason": "Unauthorized", "message": "You are not authorized! Please provide a valid access token within the header [Authorization: Bearer <accessToken>] of your request.", "code": "COMMON_UNAUTHORIZED"}"

Greetings and thanks.

Hi Raul,

Are you using the Copernicus Data Space Ecosystem endpoint? If so, you need to follow this documentation.

Hi William,

I have read that documentation, but I find it strange that the authentication method I use here works in other scripts without any problem but not in this one.

Also if I use this method of authentication I get this error that refers e to the documentation for authentication using shconfig.

ValueError: Configuration parameters 'sh_client_id' and 'sh_client_secret' have to be set in order to authenticate with Sentinel Hub service. Check https://sentinelhub-py.readthedocs.io/en/latest/configure.html for more info.

This error points to this line of the code:

data = SentinelHubDownloadClient().download(list_of_requests, max_threads=5)

Greetings.

If this is using Copernicus Data Space Ecosystem, then we should be using this community forum. If that is the case, please can you repost there, as this maybe an issue specific to that endpoint.

If you are using the https://services.sentinel-hub.com/ endpoint then please confirm and we can continue here.