{"openapi":"3.1.0","info":{"title":"marve-py-product-service","version":"0.1.0"},"paths":{"/products/features/models/references":{"get":{"tags":["Features"],"summary":"References of every model feature","description":"Model features only, unpaged. Filter with `all` or any feature field, as on the features listing.","operationId":"get_feature_references_products_features_models_references_get","parameters":[{"name":"ordering","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"`field` ascending, `-field` descending. Default `-created_at_timestamp`.","examples":["feature_name"],"title":"Ordering"},"description":"`field` ascending, `-field` descending. Default `-created_at_timestamp`."},{"name":"all","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Search across the text fields.","title":"All"},"description":"Search across the text fields."},{"name":"feature_name","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Partial, case-insensitive.","title":"Feature Name"},"description":"Partial, case-insensitive."}],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"type":"object","properties":{"items":{"type":"array","items":{"type":"string"}}},"required":["items"],"description":"Just the references of the matching features, unpaged.","additionalProperties":true},"example":{"items":["22222222-2222-2222-2222-222222222222"]}}}},"500":{"description":"Unexpected failure. Quote `error_reference` when reporting it","content":{"application/json":{"schema":{"type":"object","properties":{"error_reference":{"type":"string","description":"Quote this when reporting a failure."},"error_type":{"type":"string"},"errors":{"type":"object","properties":{},"description":"Field name, or `message`, to a list of messages.","additionalProperties":true},"status_code":{"type":"integer"},"timestamp":{"type":"string"}},"required":["error_reference","error_type","errors","status_code","timestamp"],"description":"Every error from this service, whatever the cause. Rendered by InfrastructureError.","additionalProperties":true},"example":{"error_reference":"b7825cd3-59dc-43a9-9e1f-964676d3cf72","error_type":"NOT_FOUND_ERROR","errors":{"message":["Product with reference 11111111-1111-1111-1111-111111111111 not found."]},"status_code":500,"timestamp":"2026-09-30T10:00:00.000000"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/products/features/ports/references":{"get":{"tags":["Features"],"summary":"References of every port feature","description":"Port features only, unpaged. Filter with `all` or any feature field, as on the features listing.","operationId":"get_port_feature_references_products_features_ports_references_get","parameters":[{"name":"ordering","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"`field` ascending, `-field` descending. Default `-created_at_timestamp`.","examples":["feature_name"],"title":"Ordering"},"description":"`field` ascending, `-field` descending. Default `-created_at_timestamp`."},{"name":"all","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Search across the text fields.","title":"All"},"description":"Search across the text fields."},{"name":"feature_name","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Partial, case-insensitive.","title":"Feature Name"},"description":"Partial, case-insensitive."}],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"type":"object","properties":{"items":{"type":"array","items":{"type":"string"}}},"required":["items"],"description":"Just the references of the matching features, unpaged.","additionalProperties":true},"example":{"items":["22222222-2222-2222-2222-222222222222"]}}}},"500":{"description":"Unexpected failure. Quote `error_reference` when reporting it","content":{"application/json":{"schema":{"type":"object","properties":{"error_reference":{"type":"string","description":"Quote this when reporting a failure."},"error_type":{"type":"string"},"errors":{"type":"object","properties":{},"description":"Field name, or `message`, to a list of messages.","additionalProperties":true},"status_code":{"type":"integer"},"timestamp":{"type":"string"}},"required":["error_reference","error_type","errors","status_code","timestamp"],"description":"Every error from this service, whatever the cause. Rendered by InfrastructureError.","additionalProperties":true},"example":{"error_reference":"b7825cd3-59dc-43a9-9e1f-964676d3cf72","error_type":"NOT_FOUND_ERROR","errors":{"message":["Product with reference 11111111-1111-1111-1111-111111111111 not found."]},"status_code":500,"timestamp":"2026-09-30T10:00:00.000000"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/products/features/models":{"get":{"tags":["Features"],"summary":"Model features as reference and name","description":"Model features only, unpaged. Filter with `all` or any feature field, as on the features listing.","operationId":"get_feature_references_and_names_products_features_models_get","parameters":[{"name":"ordering","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"`field` ascending, `-field` descending. Default `-created_at_timestamp`.","examples":["feature_name"],"title":"Ordering"},"description":"`field` ascending, `-field` descending. Default `-created_at_timestamp`."},{"name":"all","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Search across the text fields.","title":"All"},"description":"Search across the text fields."},{"name":"feature_name","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Partial, case-insensitive.","title":"Feature Name"},"description":"Partial, case-insensitive."}],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"type":"object","properties":{"items":{"type":"array","items":{"type":"object","properties":{"reference":{"type":"string"},"name":{"type":"string"}},"required":["reference","name"],"additionalProperties":true}}},"required":["items"],"description":"The matching features as reference and name pairs, unpaged.","additionalProperties":true},"example":{"items":[{"reference":"22222222-2222-2222-2222-222222222222","name":"Tidal streams"}]}}}},"500":{"description":"Unexpected failure. Quote `error_reference` when reporting it","content":{"application/json":{"schema":{"type":"object","properties":{"error_reference":{"type":"string","description":"Quote this when reporting a failure."},"error_type":{"type":"string"},"errors":{"type":"object","properties":{},"description":"Field name, or `message`, to a list of messages.","additionalProperties":true},"status_code":{"type":"integer"},"timestamp":{"type":"string"}},"required":["error_reference","error_type","errors","status_code","timestamp"],"description":"Every error from this service, whatever the cause. Rendered by InfrastructureError.","additionalProperties":true},"example":{"error_reference":"b7825cd3-59dc-43a9-9e1f-964676d3cf72","error_type":"NOT_FOUND_ERROR","errors":{"message":["Product with reference 11111111-1111-1111-1111-111111111111 not found."]},"status_code":500,"timestamp":"2026-09-30T10:00:00.000000"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/products/features/ports":{"get":{"tags":["Features"],"summary":"Port features as reference and name","description":"Port features only, unpaged. Filter with `all` or any feature field, as on the features listing.","operationId":"get_port_feature_references_and_names_products_features_ports_get","parameters":[{"name":"ordering","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"`field` ascending, `-field` descending. Default `-created_at_timestamp`.","examples":["feature_name"],"title":"Ordering"},"description":"`field` ascending, `-field` descending. Default `-created_at_timestamp`."},{"name":"all","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Search across the text fields.","title":"All"},"description":"Search across the text fields."},{"name":"feature_name","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Partial, case-insensitive.","title":"Feature Name"},"description":"Partial, case-insensitive."}],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"type":"object","properties":{"items":{"type":"array","items":{"type":"object","properties":{"reference":{"type":"string"},"name":{"type":"string"}},"required":["reference","name"],"additionalProperties":true}}},"required":["items"],"description":"The matching features as reference and name pairs, unpaged.","additionalProperties":true},"example":{"items":[{"reference":"22222222-2222-2222-2222-222222222222","name":"Tidal streams"}]}}}},"500":{"description":"Unexpected failure. Quote `error_reference` when reporting it","content":{"application/json":{"schema":{"type":"object","properties":{"error_reference":{"type":"string","description":"Quote this when reporting a failure."},"error_type":{"type":"string"},"errors":{"type":"object","properties":{},"description":"Field name, or `message`, to a list of messages.","additionalProperties":true},"status_code":{"type":"integer"},"timestamp":{"type":"string"}},"required":["error_reference","error_type","errors","status_code","timestamp"],"description":"Every error from this service, whatever the cause. Rendered by InfrastructureError.","additionalProperties":true},"example":{"error_reference":"b7825cd3-59dc-43a9-9e1f-964676d3cf72","error_type":"NOT_FOUND_ERROR","errors":{"message":["Product with reference 11111111-1111-1111-1111-111111111111 not found."]},"status_code":500,"timestamp":"2026-09-30T10:00:00.000000"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/products/features/{feature_reference}":{"put":{"tags":["Features"],"summary":"Update a feature","description":"Partial: send only what changes. The change is carried into the products and bundles that hold the feature.","operationId":"update_feature_products_features__feature_reference__put","security":[{"HTTPBearer":[]}],"parameters":[{"name":"feature_reference","in":"path","required":true,"schema":{"type":"string","title":"Feature Reference"}}],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"type":"object","properties":{"reference":{"type":"string"}},"required":["reference"],"description":"The reference of the record that was created, changed or removed.","additionalProperties":true},"example":{"reference":"11111111-1111-1111-1111-111111111111"}}}},"400":{"description":"The body or a value in it is not valid","content":{"application/json":{"schema":{"type":"object","properties":{"error_reference":{"type":"string","description":"Quote this when reporting a failure."},"error_type":{"type":"string"},"errors":{"type":"object","properties":{},"description":"Field name, or `message`, to a list of messages.","additionalProperties":true},"status_code":{"type":"integer"},"timestamp":{"type":"string"}},"required":["error_reference","error_type","errors","status_code","timestamp"],"description":"Every error from this service, whatever the cause. Rendered by InfrastructureError.","additionalProperties":true},"example":{"error_reference":"b7825cd3-59dc-43a9-9e1f-964676d3cf72","error_type":"NOT_FOUND_ERROR","errors":{"message":["Product with reference 11111111-1111-1111-1111-111111111111 not found."]},"status_code":400,"timestamp":"2026-09-30T10:00:00.000000"}}}},"401":{"description":"No token, an expired token, or not allowed to do this","content":{"application/json":{"schema":{"type":"object","properties":{"error_reference":{"type":"string","description":"Quote this when reporting a failure."},"error_type":{"type":"string"},"errors":{"type":"object","properties":{},"description":"Field name, or `message`, to a list of messages.","additionalProperties":true},"status_code":{"type":"integer"},"timestamp":{"type":"string"}},"required":["error_reference","error_type","errors","status_code","timestamp"],"description":"Every error from this service, whatever the cause. Rendered by InfrastructureError.","additionalProperties":true},"example":{"error_reference":"b7825cd3-59dc-43a9-9e1f-964676d3cf72","error_type":"NOT_FOUND_ERROR","errors":{"message":["Product with reference 11111111-1111-1111-1111-111111111111 not found."]},"status_code":401,"timestamp":"2026-09-30T10:00:00.000000"}}}},"404":{"description":"No such record","content":{"application/json":{"schema":{"type":"object","properties":{"error_reference":{"type":"string","description":"Quote this when reporting a failure."},"error_type":{"type":"string"},"errors":{"type":"object","properties":{},"description":"Field name, or `message`, to a list of messages.","additionalProperties":true},"status_code":{"type":"integer"},"timestamp":{"type":"string"}},"required":["error_reference","error_type","errors","status_code","timestamp"],"description":"Every error from this service, whatever the cause. Rendered by InfrastructureError.","additionalProperties":true},"example":{"error_reference":"b7825cd3-59dc-43a9-9e1f-964676d3cf72","error_type":"NOT_FOUND_ERROR","errors":{"message":["Product with reference 11111111-1111-1111-1111-111111111111 not found."]},"status_code":404,"timestamp":"2026-09-30T10:00:00.000000"}}}},"409":{"description":"A record with that name already exists","content":{"application/json":{"schema":{"type":"object","properties":{"error_reference":{"type":"string","description":"Quote this when reporting a failure."},"error_type":{"type":"string"},"errors":{"type":"object","properties":{},"description":"Field name, or `message`, to a list of messages.","additionalProperties":true},"status_code":{"type":"integer"},"timestamp":{"type":"string"}},"required":["error_reference","error_type","errors","status_code","timestamp"],"description":"Every error from this service, whatever the cause. Rendered by InfrastructureError.","additionalProperties":true},"example":{"error_reference":"b7825cd3-59dc-43a9-9e1f-964676d3cf72","error_type":"NOT_FOUND_ERROR","errors":{"message":["Product with reference 11111111-1111-1111-1111-111111111111 not found."]},"status_code":409,"timestamp":"2026-09-30T10:00:00.000000"}}}},"500":{"description":"Unexpected failure. Quote `error_reference` when reporting it","content":{"application/json":{"schema":{"type":"object","properties":{"error_reference":{"type":"string","description":"Quote this when reporting a failure."},"error_type":{"type":"string"},"errors":{"type":"object","properties":{},"description":"Field name, or `message`, to a list of messages.","additionalProperties":true},"status_code":{"type":"integer"},"timestamp":{"type":"string"}},"required":["error_reference","error_type","errors","status_code","timestamp"],"description":"Every error from this service, whatever the cause. Rendered by InfrastructureError.","additionalProperties":true},"example":{"error_reference":"b7825cd3-59dc-43a9-9e1f-964676d3cf72","error_type":"NOT_FOUND_ERROR","errors":{"message":["Product with reference 11111111-1111-1111-1111-111111111111 not found."]},"status_code":500,"timestamp":"2026-09-30T10:00:00.000000"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}},"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"feature_name":{"type":["string","null"]},"description":{"type":["string","null"]},"feature_type":{"type":["string","null"]},"base_price":{"type":["number","null"]},"prediction":{"type":["integer","null"]},"hindcast":{"type":["integer","null"]},"box_size":{"type":["string","null"]},"price_multiplier":{"type":["number","null"]},"boxes":{"type":["array","null"],"items":{"type":"object","properties":{"lat_min":{"type":"number"},"lat_max":{"type":"number"},"lon_min":{"type":"number"},"lon_max":{"type":"number"},"coverage":{"type":"number"},"bath_value":{"type":"number"}},"additionalProperties":true}},"metadata":{"type":["object","null"],"additionalProperties":true},"updated_at_timestamp":{"type":"string","description":"Optional. Generated by the service when left out."}},"description":"Send only what changes."},"example":{"description":"Updated description","base_price":120.0}}}}},"delete":{"tags":["Features"],"summary":"Delete a feature","description":"Also removes it from every product and bundle that holds it.","operationId":"delete_feature_products_features__feature_reference__delete","security":[{"HTTPBearer":[]}],"parameters":[{"name":"feature_reference","in":"path","required":true,"schema":{"type":"string","title":"Feature Reference"}}],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"type":"object","properties":{"reference":{"type":"string"},"message":{"type":"string"}},"required":["reference","message"],"additionalProperties":true},"example":{"reference":"44444444-4444-4444-4444-444444444444","message":"Feature 22222222-2222-2222-2222-222222222222 removed from bundle 44444444-4444-4444-4444-444444444444"}}}},"401":{"description":"No token, an expired token, or not allowed to do this","content":{"application/json":{"schema":{"type":"object","properties":{"error_reference":{"type":"string","description":"Quote this when reporting a failure."},"error_type":{"type":"string"},"errors":{"type":"object","properties":{},"description":"Field name, or `message`, to a list of messages.","additionalProperties":true},"status_code":{"type":"integer"},"timestamp":{"type":"string"}},"required":["error_reference","error_type","errors","status_code","timestamp"],"description":"Every error from this service, whatever the cause. Rendered by InfrastructureError.","additionalProperties":true},"example":{"error_reference":"b7825cd3-59dc-43a9-9e1f-964676d3cf72","error_type":"NOT_FOUND_ERROR","errors":{"message":["Product with reference 11111111-1111-1111-1111-111111111111 not found."]},"status_code":401,"timestamp":"2026-09-30T10:00:00.000000"}}}},"404":{"description":"No such record","content":{"application/json":{"schema":{"type":"object","properties":{"error_reference":{"type":"string","description":"Quote this when reporting a failure."},"error_type":{"type":"string"},"errors":{"type":"object","properties":{},"description":"Field name, or `message`, to a list of messages.","additionalProperties":true},"status_code":{"type":"integer"},"timestamp":{"type":"string"}},"required":["error_reference","error_type","errors","status_code","timestamp"],"description":"Every error from this service, whatever the cause. Rendered by InfrastructureError.","additionalProperties":true},"example":{"error_reference":"b7825cd3-59dc-43a9-9e1f-964676d3cf72","error_type":"NOT_FOUND_ERROR","errors":{"message":["Product with reference 11111111-1111-1111-1111-111111111111 not found."]},"status_code":404,"timestamp":"2026-09-30T10:00:00.000000"}}}},"500":{"description":"Unexpected failure. Quote `error_reference` when reporting it","content":{"application/json":{"schema":{"type":"object","properties":{"error_reference":{"type":"string","description":"Quote this when reporting a failure."},"error_type":{"type":"string"},"errors":{"type":"object","properties":{},"description":"Field name, or `message`, to a list of messages.","additionalProperties":true},"status_code":{"type":"integer"},"timestamp":{"type":"string"}},"required":["error_reference","error_type","errors","status_code","timestamp"],"description":"Every error from this service, whatever the cause. Rendered by InfrastructureError.","additionalProperties":true},"example":{"error_reference":"b7825cd3-59dc-43a9-9e1f-964676d3cf72","error_type":"NOT_FOUND_ERROR","errors":{"message":["Product with reference 11111111-1111-1111-1111-111111111111 not found."]},"status_code":500,"timestamp":"2026-09-30T10:00:00.000000"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/products/features":{"get":{"tags":["Features"],"summary":"List features","description":"Paged, ten to a page. Any field of the record can be used as a filter\n(`?field=value`), not only the ones listed; text is matched partially and case-insensitively.\n`all` searches across the text fields instead. `ordering=field` sorts ascending,\n`ordering=-field` descending; the default is newest first.","operationId":"get_features_products_features_get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"page","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Page number.","examples":["1"],"title":"Page"},"description":"Page number."},{"name":"ordering","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"`field` ascending, `-field` descending. Default `-created_at_timestamp`.","examples":["feature_name"],"title":"Ordering"},"description":"`field` ascending, `-field` descending. Default `-created_at_timestamp`."},{"name":"all","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Search across the text fields.","title":"All"},"description":"Search across the text fields."},{"name":"feature_name","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Partial, case-insensitive.","title":"Feature Name"},"description":"Partial, case-insensitive."},{"name":"feature_type","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"For example `model`, `port` or `data`.","title":"Feature Type"},"description":"For example `model`, `port` or `data`."}],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"type":"object","properties":{"items":{"type":"array","items":{"type":"object","properties":{"reference":{"type":"string"},"feature_name":{"type":"string"},"description":{"type":["string","null"]},"feature_type":{"type":"string","description":"For example `model`, `port` or `data`."},"base_price":{"type":"number"},"prediction":{"type":["integer","null"],"description":"Model features only; null otherwise."},"hindcast":{"type":["integer","null"],"description":"Model features only; null otherwise."},"box_size":{"type":["string","null"],"description":"`NxN` in degrees. Model features only."},"price_multiplier":{"type":["number","null"],"description":"Price per extra box. Model features only."},"min_boxes":{"type":"integer"},"boxes":{"type":"array","description":"Geographic boxes. Model features only.","items":{"type":"object","properties":{"lat_min":{"type":"number"},"lat_max":{"type":"number"},"lon_min":{"type":"number"},"lon_max":{"type":"number"},"coverage":{"type":"number"},"bath_value":{"type":"number"}},"additionalProperties":true}},"total_price":{"type":"number"},"metadata":{"type":"object","additionalProperties":true},"created_by_reference":{"type":["string","null"]},"updated_by_reference":{"type":["string","null"]},"created_at_timestamp":{"type":["string","null"]},"updated_at_timestamp":{"type":["string","null"]}},"required":["reference","feature_name","description","feature_type","base_price","prediction","hindcast","box_size","price_multiplier","min_boxes","boxes","total_price","metadata","created_by_reference","updated_by_reference","created_at_timestamp","updated_at_timestamp"],"description":"A catalogue feature, as `Feature.to_dict` renders it.","additionalProperties":true}},"pagination":{"type":"object","properties":{"current_page":{"type":"integer"},"page_size":{"type":"integer"},"total_records":{"type":"integer"},"total_pages":{"type":"integer"},"has_next":{"type":"boolean"},"has_previous":{"type":"boolean"}},"required":["current_page","page_size","total_records","total_pages","has_next","has_previous"],"additionalProperties":true}},"required":["items","pagination"],"additionalProperties":true},"example":{"items":[{"reference":"22222222-2222-2222-2222-222222222222","feature_name":"Tidal streams","description":"A catalogue feature","feature_type":"model","base_price":100.0,"prediction":1,"hindcast":1,"box_size":"1x1","price_multiplier":1.0,"min_boxes":1,"boxes":[{"lat_min":50.0,"lat_max":51.0,"lon_min":-2.0,"lon_max":-1.0,"coverage":100.0,"bath_value":10.0}],"total_price":100.0,"metadata":{},"created_by_reference":"33333333-3333-3333-3333-333333333333","updated_by_reference":"33333333-3333-3333-3333-333333333333","created_at_timestamp":"2026-01-01T09:30:00","updated_at_timestamp":"2026-01-01T09:30:00"}],"pagination":{"current_page":1,"page_size":10,"total_records":1,"total_pages":1,"has_next":false,"has_previous":false}}}}},"401":{"description":"No token, an expired token, or not allowed to do this","content":{"application/json":{"schema":{"type":"object","properties":{"error_reference":{"type":"string","description":"Quote this when reporting a failure."},"error_type":{"type":"string"},"errors":{"type":"object","properties":{},"description":"Field name, or `message`, to a list of messages.","additionalProperties":true},"status_code":{"type":"integer"},"timestamp":{"type":"string"}},"required":["error_reference","error_type","errors","status_code","timestamp"],"description":"Every error from this service, whatever the cause. Rendered by InfrastructureError.","additionalProperties":true},"example":{"error_reference":"b7825cd3-59dc-43a9-9e1f-964676d3cf72","error_type":"NOT_FOUND_ERROR","errors":{"message":["Product with reference 11111111-1111-1111-1111-111111111111 not found."]},"status_code":401,"timestamp":"2026-09-30T10:00:00.000000"}}}},"500":{"description":"Unexpected failure. Quote `error_reference` when reporting it","content":{"application/json":{"schema":{"type":"object","properties":{"error_reference":{"type":"string","description":"Quote this when reporting a failure."},"error_type":{"type":"string"},"errors":{"type":"object","properties":{},"description":"Field name, or `message`, to a list of messages.","additionalProperties":true},"status_code":{"type":"integer"},"timestamp":{"type":"string"}},"required":["error_reference","error_type","errors","status_code","timestamp"],"description":"Every error from this service, whatever the cause. Rendered by InfrastructureError.","additionalProperties":true},"example":{"error_reference":"b7825cd3-59dc-43a9-9e1f-964676d3cf72","error_type":"NOT_FOUND_ERROR","errors":{"message":["Product with reference 11111111-1111-1111-1111-111111111111 not found."]},"status_code":500,"timestamp":"2026-09-30T10:00:00.000000"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"post":{"tags":["Features"],"summary":"Create a feature","description":"The name must be unique. Box fields apply to `model` features only.","operationId":"create_feature_products_features_post","security":[{"HTTPBearer":[]}],"responses":{"201":{"description":"Created","content":{"application/json":{"schema":{"type":"object","properties":{"reference":{"type":"string"}},"required":["reference"],"description":"The reference of the record that was created, changed or removed.","additionalProperties":true},"example":{"reference":"11111111-1111-1111-1111-111111111111"}}}},"400":{"description":"The body or a value in it is not valid","content":{"application/json":{"schema":{"type":"object","properties":{"error_reference":{"type":"string","description":"Quote this when reporting a failure."},"error_type":{"type":"string"},"errors":{"type":"object","properties":{},"description":"Field name, or `message`, to a list of messages.","additionalProperties":true},"status_code":{"type":"integer"},"timestamp":{"type":"string"}},"required":["error_reference","error_type","errors","status_code","timestamp"],"description":"Every error from this service, whatever the cause. Rendered by InfrastructureError.","additionalProperties":true},"example":{"error_reference":"b7825cd3-59dc-43a9-9e1f-964676d3cf72","error_type":"NOT_FOUND_ERROR","errors":{"message":["Product with reference 11111111-1111-1111-1111-111111111111 not found."]},"status_code":400,"timestamp":"2026-09-30T10:00:00.000000"}}}},"401":{"description":"No token, an expired token, or not allowed to do this","content":{"application/json":{"schema":{"type":"object","properties":{"error_reference":{"type":"string","description":"Quote this when reporting a failure."},"error_type":{"type":"string"},"errors":{"type":"object","properties":{},"description":"Field name, or `message`, to a list of messages.","additionalProperties":true},"status_code":{"type":"integer"},"timestamp":{"type":"string"}},"required":["error_reference","error_type","errors","status_code","timestamp"],"description":"Every error from this service, whatever the cause. Rendered by InfrastructureError.","additionalProperties":true},"example":{"error_reference":"b7825cd3-59dc-43a9-9e1f-964676d3cf72","error_type":"NOT_FOUND_ERROR","errors":{"message":["Product with reference 11111111-1111-1111-1111-111111111111 not found."]},"status_code":401,"timestamp":"2026-09-30T10:00:00.000000"}}}},"409":{"description":"A record with that name already exists","content":{"application/json":{"schema":{"type":"object","properties":{"error_reference":{"type":"string","description":"Quote this when reporting a failure."},"error_type":{"type":"string"},"errors":{"type":"object","properties":{},"description":"Field name, or `message`, to a list of messages.","additionalProperties":true},"status_code":{"type":"integer"},"timestamp":{"type":"string"}},"required":["error_reference","error_type","errors","status_code","timestamp"],"description":"Every error from this service, whatever the cause. Rendered by InfrastructureError.","additionalProperties":true},"example":{"error_reference":"b7825cd3-59dc-43a9-9e1f-964676d3cf72","error_type":"NOT_FOUND_ERROR","errors":{"message":["Product with reference 11111111-1111-1111-1111-111111111111 not found."]},"status_code":409,"timestamp":"2026-09-30T10:00:00.000000"}}}},"500":{"description":"Unexpected failure. Quote `error_reference` when reporting it","content":{"application/json":{"schema":{"type":"object","properties":{"error_reference":{"type":"string","description":"Quote this when reporting a failure."},"error_type":{"type":"string"},"errors":{"type":"object","properties":{},"description":"Field name, or `message`, to a list of messages.","additionalProperties":true},"status_code":{"type":"integer"},"timestamp":{"type":"string"}},"required":["error_reference","error_type","errors","status_code","timestamp"],"description":"Every error from this service, whatever the cause. Rendered by InfrastructureError.","additionalProperties":true},"example":{"error_reference":"b7825cd3-59dc-43a9-9e1f-964676d3cf72","error_type":"NOT_FOUND_ERROR","errors":{"message":["Product with reference 11111111-1111-1111-1111-111111111111 not found."]},"status_code":500,"timestamp":"2026-09-30T10:00:00.000000"}}}}},"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"feature_name":{"type":"string"},"description":{"type":"string"},"feature_type":{"type":"string","description":"For example `model`, `port` or `data`."},"base_price":{"type":"number"},"price_multiplier":{"type":["number","null"],"description":"Price per extra box. Model features only; defaults to 0."},"prediction":{"type":["integer","null"],"description":"Model features only; defaults to 1."},"hindcast":{"type":["integer","null"],"description":"Model features only; defaults to 1."},"box_size":{"type":["string","null"],"description":"`NxN` in degrees. Model features only; defaults to `1x1`."},"min_boxes":{"type":["integer","null"],"description":"Model features only; defaults to 1."},"boxes":{"type":"array","description":"Geographic boxes.","items":{"type":"object","properties":{"lat_min":{"type":"number"},"lat_max":{"type":"number"},"lon_min":{"type":"number"},"lon_max":{"type":"number"},"coverage":{"type":"number"},"bath_value":{"type":"number"}},"additionalProperties":true}},"metadata":{"type":"object","additionalProperties":true},"created_at_timestamp":{"type":"string","description":"Optional. Generated by the service when left out."},"updated_at_timestamp":{"type":"string","description":"Optional. Generated by the service when left out."},"reference":{"type":"string","description":"Optional. Generated by the service when left out."}},"required":["feature_name","description","feature_type","base_price"]},"example":{"feature_name":"Tidal streams","description":"A catalogue feature","feature_type":"model","base_price":100.0,"box_size":"1x1","price_multiplier":1.0,"min_boxes":1,"boxes":[{"lat_min":50.0,"lat_max":51.0,"lon_min":-2.0,"lon_max":-1.0,"coverage":100.0,"bath_value":10.0}]}}}}}},"/products/bundles/{bundle_reference}/features/{feature_reference}":{"delete":{"tags":["Bundles"],"summary":"Remove a feature from a bundle","operationId":"remove_bundle_feature_products_bundles__bundle_reference__features__feature_reference__delete","security":[{"HTTPBearer":[]}],"parameters":[{"name":"bundle_reference","in":"path","required":true,"schema":{"type":"string","title":"Bundle Reference"}},{"name":"feature_reference","in":"path","required":true,"schema":{"type":"string","title":"Feature Reference"}}],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"type":"object","properties":{"reference":{"type":"string"},"message":{"type":"string"}},"required":["reference","message"],"additionalProperties":true},"example":{"reference":"44444444-4444-4444-4444-444444444444","message":"Feature 22222222-2222-2222-2222-222222222222 removed from bundle 44444444-4444-4444-4444-444444444444"}}}},"401":{"description":"No token, an expired token, or not allowed to do this","content":{"application/json":{"schema":{"type":"object","properties":{"error_reference":{"type":"string","description":"Quote this when reporting a failure."},"error_type":{"type":"string"},"errors":{"type":"object","properties":{},"description":"Field name, or `message`, to a list of messages.","additionalProperties":true},"status_code":{"type":"integer"},"timestamp":{"type":"string"}},"required":["error_reference","error_type","errors","status_code","timestamp"],"description":"Every error from this service, whatever the cause. Rendered by InfrastructureError.","additionalProperties":true},"example":{"error_reference":"b7825cd3-59dc-43a9-9e1f-964676d3cf72","error_type":"NOT_FOUND_ERROR","errors":{"message":["Product with reference 11111111-1111-1111-1111-111111111111 not found."]},"status_code":401,"timestamp":"2026-09-30T10:00:00.000000"}}}},"404":{"description":"No such record","content":{"application/json":{"schema":{"type":"object","properties":{"error_reference":{"type":"string","description":"Quote this when reporting a failure."},"error_type":{"type":"string"},"errors":{"type":"object","properties":{},"description":"Field name, or `message`, to a list of messages.","additionalProperties":true},"status_code":{"type":"integer"},"timestamp":{"type":"string"}},"required":["error_reference","error_type","errors","status_code","timestamp"],"description":"Every error from this service, whatever the cause. Rendered by InfrastructureError.","additionalProperties":true},"example":{"error_reference":"b7825cd3-59dc-43a9-9e1f-964676d3cf72","error_type":"NOT_FOUND_ERROR","errors":{"message":["Product with reference 11111111-1111-1111-1111-111111111111 not found."]},"status_code":404,"timestamp":"2026-09-30T10:00:00.000000"}}}},"500":{"description":"Unexpected failure. Quote `error_reference` when reporting it","content":{"application/json":{"schema":{"type":"object","properties":{"error_reference":{"type":"string","description":"Quote this when reporting a failure."},"error_type":{"type":"string"},"errors":{"type":"object","properties":{},"description":"Field name, or `message`, to a list of messages.","additionalProperties":true},"status_code":{"type":"integer"},"timestamp":{"type":"string"}},"required":["error_reference","error_type","errors","status_code","timestamp"],"description":"Every error from this service, whatever the cause. Rendered by InfrastructureError.","additionalProperties":true},"example":{"error_reference":"b7825cd3-59dc-43a9-9e1f-964676d3cf72","error_type":"NOT_FOUND_ERROR","errors":{"message":["Product with reference 11111111-1111-1111-1111-111111111111 not found."]},"status_code":500,"timestamp":"2026-09-30T10:00:00.000000"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/products/bundles/{bundle_reference}/features":{"get":{"tags":["Bundles"],"summary":"A bundle's features","description":"The catalogue record of each feature on the bundle, in one page.","operationId":"get_bundle_features_products_bundles__bundle_reference__features_get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"bundle_reference","in":"path","required":true,"schema":{"type":"string","title":"Bundle Reference"}}],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"type":"object","properties":{"items":{"type":"array","items":{"type":"object","properties":{"reference":{"type":"string"},"feature_name":{"type":"string"},"description":{"type":["string","null"]},"feature_type":{"type":"string","description":"For example `model`, `port` or `data`."},"base_price":{"type":"number"},"prediction":{"type":["integer","null"],"description":"Model features only; null otherwise."},"hindcast":{"type":["integer","null"],"description":"Model features only; null otherwise."},"box_size":{"type":["string","null"],"description":"`NxN` in degrees. Model features only."},"price_multiplier":{"type":["number","null"],"description":"Price per extra box. Model features only."},"min_boxes":{"type":"integer"},"boxes":{"type":"array","description":"Geographic boxes. Model features only.","items":{"type":"object","properties":{"lat_min":{"type":"number"},"lat_max":{"type":"number"},"lon_min":{"type":"number"},"lon_max":{"type":"number"},"coverage":{"type":"number"},"bath_value":{"type":"number"}},"additionalProperties":true}},"total_price":{"type":"number"},"metadata":{"type":"object","additionalProperties":true},"created_by_reference":{"type":["string","null"]},"updated_by_reference":{"type":["string","null"]},"created_at_timestamp":{"type":["string","null"]},"updated_at_timestamp":{"type":["string","null"]}},"required":["reference","feature_name","description","feature_type","base_price","prediction","hindcast","box_size","price_multiplier","min_boxes","boxes","total_price","metadata","created_by_reference","updated_by_reference","created_at_timestamp","updated_at_timestamp"],"description":"A catalogue feature, as `Feature.to_dict` renders it.","additionalProperties":true}},"pagination":{"type":"object","properties":{"current_page":{"type":"integer"},"page_size":{"type":"integer"},"total_records":{"type":"integer"},"total_pages":{"type":"integer"},"has_next":{"type":"boolean"},"has_previous":{"type":"boolean"}},"required":["current_page","page_size","total_records","total_pages","has_next","has_previous"],"additionalProperties":true}},"required":["items","pagination"],"additionalProperties":true},"example":{"items":[{"reference":"22222222-2222-2222-2222-222222222222","feature_name":"Tidal streams","description":"A catalogue feature","feature_type":"model","base_price":100.0,"prediction":1,"hindcast":1,"box_size":"1x1","price_multiplier":1.0,"min_boxes":1,"boxes":[{"lat_min":50.0,"lat_max":51.0,"lon_min":-2.0,"lon_max":-1.0,"coverage":100.0,"bath_value":10.0}],"total_price":100.0,"metadata":{},"created_by_reference":"33333333-3333-3333-3333-333333333333","updated_by_reference":"33333333-3333-3333-3333-333333333333","created_at_timestamp":"2026-01-01T09:30:00","updated_at_timestamp":"2026-01-01T09:30:00"}],"pagination":{"current_page":1,"page_size":10,"total_records":1,"total_pages":1,"has_next":false,"has_previous":false}}}}},"401":{"description":"No token, an expired token, or not allowed to do this","content":{"application/json":{"schema":{"type":"object","properties":{"error_reference":{"type":"string","description":"Quote this when reporting a failure."},"error_type":{"type":"string"},"errors":{"type":"object","properties":{},"description":"Field name, or `message`, to a list of messages.","additionalProperties":true},"status_code":{"type":"integer"},"timestamp":{"type":"string"}},"required":["error_reference","error_type","errors","status_code","timestamp"],"description":"Every error from this service, whatever the cause. Rendered by InfrastructureError.","additionalProperties":true},"example":{"error_reference":"b7825cd3-59dc-43a9-9e1f-964676d3cf72","error_type":"NOT_FOUND_ERROR","errors":{"message":["Product with reference 11111111-1111-1111-1111-111111111111 not found."]},"status_code":401,"timestamp":"2026-09-30T10:00:00.000000"}}}},"404":{"description":"No such record","content":{"application/json":{"schema":{"type":"object","properties":{"error_reference":{"type":"string","description":"Quote this when reporting a failure."},"error_type":{"type":"string"},"errors":{"type":"object","properties":{},"description":"Field name, or `message`, to a list of messages.","additionalProperties":true},"status_code":{"type":"integer"},"timestamp":{"type":"string"}},"required":["error_reference","error_type","errors","status_code","timestamp"],"description":"Every error from this service, whatever the cause. Rendered by InfrastructureError.","additionalProperties":true},"example":{"error_reference":"b7825cd3-59dc-43a9-9e1f-964676d3cf72","error_type":"NOT_FOUND_ERROR","errors":{"message":["Product with reference 11111111-1111-1111-1111-111111111111 not found."]},"status_code":404,"timestamp":"2026-09-30T10:00:00.000000"}}}},"500":{"description":"Unexpected failure. Quote `error_reference` when reporting it","content":{"application/json":{"schema":{"type":"object","properties":{"error_reference":{"type":"string","description":"Quote this when reporting a failure."},"error_type":{"type":"string"},"errors":{"type":"object","properties":{},"description":"Field name, or `message`, to a list of messages.","additionalProperties":true},"status_code":{"type":"integer"},"timestamp":{"type":"string"}},"required":["error_reference","error_type","errors","status_code","timestamp"],"description":"Every error from this service, whatever the cause. Rendered by InfrastructureError.","additionalProperties":true},"example":{"error_reference":"b7825cd3-59dc-43a9-9e1f-964676d3cf72","error_type":"NOT_FOUND_ERROR","errors":{"message":["Product with reference 11111111-1111-1111-1111-111111111111 not found."]},"status_code":500,"timestamp":"2026-09-30T10:00:00.000000"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"post":{"tags":["Bundles"],"summary":"Add features to a bundle","operationId":"add_features_to_bundle_products_bundles__bundle_reference__features_post","security":[{"HTTPBearer":[]}],"parameters":[{"name":"bundle_reference","in":"path","required":true,"schema":{"type":"string","title":"Bundle Reference"}}],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"type":"object","properties":{"reference":{"type":"string"}},"required":["reference"],"description":"The reference of the record that was created, changed or removed.","additionalProperties":true},"example":{"reference":"11111111-1111-1111-1111-111111111111"}}}},"400":{"description":"The body or a value in it is not valid","content":{"application/json":{"schema":{"type":"object","properties":{"error_reference":{"type":"string","description":"Quote this when reporting a failure."},"error_type":{"type":"string"},"errors":{"type":"object","properties":{},"description":"Field name, or `message`, to a list of messages.","additionalProperties":true},"status_code":{"type":"integer"},"timestamp":{"type":"string"}},"required":["error_reference","error_type","errors","status_code","timestamp"],"description":"Every error from this service, whatever the cause. Rendered by InfrastructureError.","additionalProperties":true},"example":{"error_reference":"b7825cd3-59dc-43a9-9e1f-964676d3cf72","error_type":"NOT_FOUND_ERROR","errors":{"message":["Product with reference 11111111-1111-1111-1111-111111111111 not found."]},"status_code":400,"timestamp":"2026-09-30T10:00:00.000000"}}}},"401":{"description":"No token, an expired token, or not allowed to do this","content":{"application/json":{"schema":{"type":"object","properties":{"error_reference":{"type":"string","description":"Quote this when reporting a failure."},"error_type":{"type":"string"},"errors":{"type":"object","properties":{},"description":"Field name, or `message`, to a list of messages.","additionalProperties":true},"status_code":{"type":"integer"},"timestamp":{"type":"string"}},"required":["error_reference","error_type","errors","status_code","timestamp"],"description":"Every error from this service, whatever the cause. Rendered by InfrastructureError.","additionalProperties":true},"example":{"error_reference":"b7825cd3-59dc-43a9-9e1f-964676d3cf72","error_type":"NOT_FOUND_ERROR","errors":{"message":["Product with reference 11111111-1111-1111-1111-111111111111 not found."]},"status_code":401,"timestamp":"2026-09-30T10:00:00.000000"}}}},"404":{"description":"No such record","content":{"application/json":{"schema":{"type":"object","properties":{"error_reference":{"type":"string","description":"Quote this when reporting a failure."},"error_type":{"type":"string"},"errors":{"type":"object","properties":{},"description":"Field name, or `message`, to a list of messages.","additionalProperties":true},"status_code":{"type":"integer"},"timestamp":{"type":"string"}},"required":["error_reference","error_type","errors","status_code","timestamp"],"description":"Every error from this service, whatever the cause. Rendered by InfrastructureError.","additionalProperties":true},"example":{"error_reference":"b7825cd3-59dc-43a9-9e1f-964676d3cf72","error_type":"NOT_FOUND_ERROR","errors":{"message":["Product with reference 11111111-1111-1111-1111-111111111111 not found."]},"status_code":404,"timestamp":"2026-09-30T10:00:00.000000"}}}},"500":{"description":"Unexpected failure. Quote `error_reference` when reporting it","content":{"application/json":{"schema":{"type":"object","properties":{"error_reference":{"type":"string","description":"Quote this when reporting a failure."},"error_type":{"type":"string"},"errors":{"type":"object","properties":{},"description":"Field name, or `message`, to a list of messages.","additionalProperties":true},"status_code":{"type":"integer"},"timestamp":{"type":"string"}},"required":["error_reference","error_type","errors","status_code","timestamp"],"description":"Every error from this service, whatever the cause. Rendered by InfrastructureError.","additionalProperties":true},"example":{"error_reference":"b7825cd3-59dc-43a9-9e1f-964676d3cf72","error_type":"NOT_FOUND_ERROR","errors":{"message":["Product with reference 11111111-1111-1111-1111-111111111111 not found."]},"status_code":500,"timestamp":"2026-09-30T10:00:00.000000"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}},"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"references":{"type":"array","description":"Feature references.","items":{"type":"string"}}},"required":["references"]},"example":{"references":["22222222-2222-2222-2222-222222222222"]}}}}}},"/products/bundles/{bundle_reference}/discounts":{"post":{"tags":["Bundles"],"summary":"Put a discount on a bundle","operationId":"add_discount_to_bundle_by_reference_products_bundles__bundle_reference__discounts_post","security":[{"HTTPBearer":[]}],"parameters":[{"name":"bundle_reference","in":"path","required":true,"schema":{"type":"string","title":"Bundle Reference"}}],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"type":"object","properties":{"reference":{"type":"string"}},"required":["reference"],"description":"The reference of the record that was created, changed or removed.","additionalProperties":true},"example":{"reference":"11111111-1111-1111-1111-111111111111"}}}},"400":{"description":"The body or a value in it is not valid","content":{"application/json":{"schema":{"type":"object","properties":{"error_reference":{"type":"string","description":"Quote this when reporting a failure."},"error_type":{"type":"string"},"errors":{"type":"object","properties":{},"description":"Field name, or `message`, to a list of messages.","additionalProperties":true},"status_code":{"type":"integer"},"timestamp":{"type":"string"}},"required":["error_reference","error_type","errors","status_code","timestamp"],"description":"Every error from this service, whatever the cause. Rendered by InfrastructureError.","additionalProperties":true},"example":{"error_reference":"b7825cd3-59dc-43a9-9e1f-964676d3cf72","error_type":"NOT_FOUND_ERROR","errors":{"message":["Product with reference 11111111-1111-1111-1111-111111111111 not found."]},"status_code":400,"timestamp":"2026-09-30T10:00:00.000000"}}}},"401":{"description":"No token, an expired token, or not allowed to do this","content":{"application/json":{"schema":{"type":"object","properties":{"error_reference":{"type":"string","description":"Quote this when reporting a failure."},"error_type":{"type":"string"},"errors":{"type":"object","properties":{},"description":"Field name, or `message`, to a list of messages.","additionalProperties":true},"status_code":{"type":"integer"},"timestamp":{"type":"string"}},"required":["error_reference","error_type","errors","status_code","timestamp"],"description":"Every error from this service, whatever the cause. Rendered by InfrastructureError.","additionalProperties":true},"example":{"error_reference":"b7825cd3-59dc-43a9-9e1f-964676d3cf72","error_type":"NOT_FOUND_ERROR","errors":{"message":["Product with reference 11111111-1111-1111-1111-111111111111 not found."]},"status_code":401,"timestamp":"2026-09-30T10:00:00.000000"}}}},"404":{"description":"No such record","content":{"application/json":{"schema":{"type":"object","properties":{"error_reference":{"type":"string","description":"Quote this when reporting a failure."},"error_type":{"type":"string"},"errors":{"type":"object","properties":{},"description":"Field name, or `message`, to a list of messages.","additionalProperties":true},"status_code":{"type":"integer"},"timestamp":{"type":"string"}},"required":["error_reference","error_type","errors","status_code","timestamp"],"description":"Every error from this service, whatever the cause. Rendered by InfrastructureError.","additionalProperties":true},"example":{"error_reference":"b7825cd3-59dc-43a9-9e1f-964676d3cf72","error_type":"NOT_FOUND_ERROR","errors":{"message":["Product with reference 11111111-1111-1111-1111-111111111111 not found."]},"status_code":404,"timestamp":"2026-09-30T10:00:00.000000"}}}},"500":{"description":"Unexpected failure. Quote `error_reference` when reporting it","content":{"application/json":{"schema":{"type":"object","properties":{"error_reference":{"type":"string","description":"Quote this when reporting a failure."},"error_type":{"type":"string"},"errors":{"type":"object","properties":{},"description":"Field name, or `message`, to a list of messages.","additionalProperties":true},"status_code":{"type":"integer"},"timestamp":{"type":"string"}},"required":["error_reference","error_type","errors","status_code","timestamp"],"description":"Every error from this service, whatever the cause. Rendered by InfrastructureError.","additionalProperties":true},"example":{"error_reference":"b7825cd3-59dc-43a9-9e1f-964676d3cf72","error_type":"NOT_FOUND_ERROR","errors":{"message":["Product with reference 11111111-1111-1111-1111-111111111111 not found."]},"status_code":500,"timestamp":"2026-09-30T10:00:00.000000"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}},"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"reference":{"type":"string"}},"required":["reference"]},"example":{"reference":"66666666-6666-6666-6666-666666666666"}}}}},"delete":{"tags":["Bundles"],"summary":"Take the discount off a bundle","operationId":"remove_bundle_discount_products_bundles__bundle_reference__discounts_delete","security":[{"HTTPBearer":[]}],"parameters":[{"name":"bundle_reference","in":"path","required":true,"schema":{"type":"string","title":"Bundle Reference"}}],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"type":"object","properties":{"reference":{"type":"string"},"message":{"type":"string"}},"required":["reference","message"],"additionalProperties":true},"example":{"reference":"44444444-4444-4444-4444-444444444444","message":"Feature 22222222-2222-2222-2222-222222222222 removed from bundle 44444444-4444-4444-4444-444444444444"}}}},"401":{"description":"No token, an expired token, or not allowed to do this","content":{"application/json":{"schema":{"type":"object","properties":{"error_reference":{"type":"string","description":"Quote this when reporting a failure."},"error_type":{"type":"string"},"errors":{"type":"object","properties":{},"description":"Field name, or `message`, to a list of messages.","additionalProperties":true},"status_code":{"type":"integer"},"timestamp":{"type":"string"}},"required":["error_reference","error_type","errors","status_code","timestamp"],"description":"Every error from this service, whatever the cause. Rendered by InfrastructureError.","additionalProperties":true},"example":{"error_reference":"b7825cd3-59dc-43a9-9e1f-964676d3cf72","error_type":"NOT_FOUND_ERROR","errors":{"message":["Product with reference 11111111-1111-1111-1111-111111111111 not found."]},"status_code":401,"timestamp":"2026-09-30T10:00:00.000000"}}}},"404":{"description":"No such record","content":{"application/json":{"schema":{"type":"object","properties":{"error_reference":{"type":"string","description":"Quote this when reporting a failure."},"error_type":{"type":"string"},"errors":{"type":"object","properties":{},"description":"Field name, or `message`, to a list of messages.","additionalProperties":true},"status_code":{"type":"integer"},"timestamp":{"type":"string"}},"required":["error_reference","error_type","errors","status_code","timestamp"],"description":"Every error from this service, whatever the cause. Rendered by InfrastructureError.","additionalProperties":true},"example":{"error_reference":"b7825cd3-59dc-43a9-9e1f-964676d3cf72","error_type":"NOT_FOUND_ERROR","errors":{"message":["Product with reference 11111111-1111-1111-1111-111111111111 not found."]},"status_code":404,"timestamp":"2026-09-30T10:00:00.000000"}}}},"500":{"description":"Unexpected failure. Quote `error_reference` when reporting it","content":{"application/json":{"schema":{"type":"object","properties":{"error_reference":{"type":"string","description":"Quote this when reporting a failure."},"error_type":{"type":"string"},"errors":{"type":"object","properties":{},"description":"Field name, or `message`, to a list of messages.","additionalProperties":true},"status_code":{"type":"integer"},"timestamp":{"type":"string"}},"required":["error_reference","error_type","errors","status_code","timestamp"],"description":"Every error from this service, whatever the cause. Rendered by InfrastructureError.","additionalProperties":true},"example":{"error_reference":"b7825cd3-59dc-43a9-9e1f-964676d3cf72","error_type":"NOT_FOUND_ERROR","errors":{"message":["Product with reference 11111111-1111-1111-1111-111111111111 not found."]},"status_code":500,"timestamp":"2026-09-30T10:00:00.000000"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/products/bundles/provision":{"post":{"tags":["Bundles"],"summary":"Provision a hand-built entitlement","description":"Gives somebody a product with no cart and no payment, by publishing what a paid checkout publishes. Administrators only. `email` is required and finds the person: an existing account keeps its own reference, and somebody with no account is given a placeholder (their account gets its own reference at sign-up); either way it comes back as `user_reference`, which you do not need to send. If sent, it must be the reference of the account with that email (400 otherwise). With `organisation_reference` the product goes onto the licence the organisation already holds, if any, and the person is invited (or, if already a member, emailed). The first person in an organisation is invited as its organisation_admin, everybody after as a user. 409 if they belong to a different organisation. Without `organisation_reference` it is a licence of their own: only for an existing account in no organisation (400 for no account, 409 for a member of one). No bundle is named, so the body must carry `entitlement`, naming its `product_reference` and listing at least one model in `data` (400 otherwise).","operationId":"provision_entitlement_products_bundles_provision_post","security":[{"HTTPBearer":[]}],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"type":"object","properties":{"reference":{"type":"string","description":"A receipt for the provisioning, not the licence."},"user_reference":{"type":"string","description":"Whose licence it is: the one sent, the existing account's, or a new one for somebody with no account."}},"required":["reference","user_reference"],"additionalProperties":true},"example":{"reference":"44444444-4444-4444-4444-444444444444","user_reference":"33333333-3333-3333-3333-333333333333"}}}},"400":{"description":"The body or a value in it is not valid","content":{"application/json":{"schema":{"type":"object","properties":{"error_reference":{"type":"string","description":"Quote this when reporting a failure."},"error_type":{"type":"string"},"errors":{"type":"object","properties":{},"description":"Field name, or `message`, to a list of messages.","additionalProperties":true},"status_code":{"type":"integer"},"timestamp":{"type":"string"}},"required":["error_reference","error_type","errors","status_code","timestamp"],"description":"Every error from this service, whatever the cause. Rendered by InfrastructureError.","additionalProperties":true},"example":{"error_reference":"b7825cd3-59dc-43a9-9e1f-964676d3cf72","error_type":"NOT_FOUND_ERROR","errors":{"message":["Product with reference 11111111-1111-1111-1111-111111111111 not found."]},"status_code":400,"timestamp":"2026-09-30T10:00:00.000000"}}}},"401":{"description":"No token, an expired token, or not allowed to do this","content":{"application/json":{"schema":{"type":"object","properties":{"error_reference":{"type":"string","description":"Quote this when reporting a failure."},"error_type":{"type":"string"},"errors":{"type":"object","properties":{},"description":"Field name, or `message`, to a list of messages.","additionalProperties":true},"status_code":{"type":"integer"},"timestamp":{"type":"string"}},"required":["error_reference","error_type","errors","status_code","timestamp"],"description":"Every error from this service, whatever the cause. Rendered by InfrastructureError.","additionalProperties":true},"example":{"error_reference":"b7825cd3-59dc-43a9-9e1f-964676d3cf72","error_type":"NOT_FOUND_ERROR","errors":{"message":["Product with reference 11111111-1111-1111-1111-111111111111 not found."]},"status_code":401,"timestamp":"2026-09-30T10:00:00.000000"}}}},"404":{"description":"No such record","content":{"application/json":{"schema":{"type":"object","properties":{"error_reference":{"type":"string","description":"Quote this when reporting a failure."},"error_type":{"type":"string"},"errors":{"type":"object","properties":{},"description":"Field name, or `message`, to a list of messages.","additionalProperties":true},"status_code":{"type":"integer"},"timestamp":{"type":"string"}},"required":["error_reference","error_type","errors","status_code","timestamp"],"description":"Every error from this service, whatever the cause. Rendered by InfrastructureError.","additionalProperties":true},"example":{"error_reference":"b7825cd3-59dc-43a9-9e1f-964676d3cf72","error_type":"NOT_FOUND_ERROR","errors":{"message":["Product with reference 11111111-1111-1111-1111-111111111111 not found."]},"status_code":404,"timestamp":"2026-09-30T10:00:00.000000"}}}},"409":{"description":"A record with that name already exists","content":{"application/json":{"schema":{"type":"object","properties":{"error_reference":{"type":"string","description":"Quote this when reporting a failure."},"error_type":{"type":"string"},"errors":{"type":"object","properties":{},"description":"Field name, or `message`, to a list of messages.","additionalProperties":true},"status_code":{"type":"integer"},"timestamp":{"type":"string"}},"required":["error_reference","error_type","errors","status_code","timestamp"],"description":"Every error from this service, whatever the cause. Rendered by InfrastructureError.","additionalProperties":true},"example":{"error_reference":"b7825cd3-59dc-43a9-9e1f-964676d3cf72","error_type":"NOT_FOUND_ERROR","errors":{"message":["Product with reference 11111111-1111-1111-1111-111111111111 not found."]},"status_code":409,"timestamp":"2026-09-30T10:00:00.000000"}}}},"500":{"description":"Unexpected failure. Quote `error_reference` when reporting it","content":{"application/json":{"schema":{"type":"object","properties":{"error_reference":{"type":"string","description":"Quote this when reporting a failure."},"error_type":{"type":"string"},"errors":{"type":"object","properties":{},"description":"Field name, or `message`, to a list of messages.","additionalProperties":true},"status_code":{"type":"integer"},"timestamp":{"type":"string"}},"required":["error_reference","error_type","errors","status_code","timestamp"],"description":"Every error from this service, whatever the cause. Rendered by InfrastructureError.","additionalProperties":true},"example":{"error_reference":"b7825cd3-59dc-43a9-9e1f-964676d3cf72","error_type":"NOT_FOUND_ERROR","errors":{"message":["Product with reference 11111111-1111-1111-1111-111111111111 not found."]},"status_code":500,"timestamp":"2026-09-30T10:00:00.000000"}}}}},"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"user_reference":{"type":["string","null"],"description":"Optional. Left out, an account with this email keeps its own reference, and somebody with no account is given a new one. Returned in the answer."},"email":{"type":"string","description":"The person's email. Required."},"organisation_reference":{"type":["string","null"]},"valid_from":{"type":["string","null"],"description":"YYYY-MM-DD. Defaults to today."},"entitlement":{"type":["object","null"],"description":"A hand-built grant. Required on /products/bundles/provision, which names no bundle. Must carry product_reference, the product it grants.","additionalProperties":true}},"additionalProperties":true},"example":{"email":"jane@example.com","organisation_reference":"77777777-7777-7777-7777-777777777777","entitlement":{"product_reference":"11111111-1111-1111-1111-111111111111","license_type":"bespoke","license_class":"standard","max_users":3,"valid_from":"2026-10-01","valid_to":"2027-10-01","data":[{"feature_name":"PATG","boxes":[{"lat_min":50.0,"lat_max":51.0,"lon_min":-2.0,"lon_max":-1.0,"coverage":100.0,"bath_value":10.0}]}]}}}}}}},"/products/bundles/{bundle_reference}/provision":{"post":{"tags":["Bundles"],"summary":"Provision a bundle","description":"Gives somebody a product with no cart and no payment, by publishing what a paid checkout publishes. Administrators only. `email` is required and finds the person: an existing account keeps its own reference, and somebody with no account is given a placeholder (their account gets its own reference at sign-up); either way it comes back as `user_reference`, which you do not need to send. If sent, it must be the reference of the account with that email (400 otherwise). With `organisation_reference` the product goes onto the licence the organisation already holds, if any, and the person is invited (or, if already a member, emailed). The first person in an organisation is invited as its organisation_admin, everybody after as a user. 409 if they belong to a different organisation. Without `organisation_reference` it is a licence of their own: only for an existing account in no organisation (400 for no account, 409 for a member of one). The bundle must have features.","operationId":"provision_bundle_products_bundles__bundle_reference__provision_post","security":[{"HTTPBearer":[]}],"parameters":[{"name":"bundle_reference","in":"path","required":true,"schema":{"type":"string","title":"Bundle Reference"}}],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"type":"object","properties":{"reference":{"type":"string","description":"A receipt for the provisioning, not the licence."},"user_reference":{"type":"string","description":"Whose licence it is: the one sent, the existing account's, or a new one for somebody with no account."}},"required":["reference","user_reference"],"additionalProperties":true},"example":{"reference":"44444444-4444-4444-4444-444444444444","user_reference":"33333333-3333-3333-3333-333333333333"}}}},"400":{"description":"The body or a value in it is not valid","content":{"application/json":{"schema":{"type":"object","properties":{"error_reference":{"type":"string","description":"Quote this when reporting a failure."},"error_type":{"type":"string"},"errors":{"type":"object","properties":{},"description":"Field name, or `message`, to a list of messages.","additionalProperties":true},"status_code":{"type":"integer"},"timestamp":{"type":"string"}},"required":["error_reference","error_type","errors","status_code","timestamp"],"description":"Every error from this service, whatever the cause. Rendered by InfrastructureError.","additionalProperties":true},"example":{"error_reference":"b7825cd3-59dc-43a9-9e1f-964676d3cf72","error_type":"NOT_FOUND_ERROR","errors":{"message":["Product with reference 11111111-1111-1111-1111-111111111111 not found."]},"status_code":400,"timestamp":"2026-09-30T10:00:00.000000"}}}},"401":{"description":"No token, an expired token, or not allowed to do this","content":{"application/json":{"schema":{"type":"object","properties":{"error_reference":{"type":"string","description":"Quote this when reporting a failure."},"error_type":{"type":"string"},"errors":{"type":"object","properties":{},"description":"Field name, or `message`, to a list of messages.","additionalProperties":true},"status_code":{"type":"integer"},"timestamp":{"type":"string"}},"required":["error_reference","error_type","errors","status_code","timestamp"],"description":"Every error from this service, whatever the cause. Rendered by InfrastructureError.","additionalProperties":true},"example":{"error_reference":"b7825cd3-59dc-43a9-9e1f-964676d3cf72","error_type":"NOT_FOUND_ERROR","errors":{"message":["Product with reference 11111111-1111-1111-1111-111111111111 not found."]},"status_code":401,"timestamp":"2026-09-30T10:00:00.000000"}}}},"404":{"description":"No such record","content":{"application/json":{"schema":{"type":"object","properties":{"error_reference":{"type":"string","description":"Quote this when reporting a failure."},"error_type":{"type":"string"},"errors":{"type":"object","properties":{},"description":"Field name, or `message`, to a list of messages.","additionalProperties":true},"status_code":{"type":"integer"},"timestamp":{"type":"string"}},"required":["error_reference","error_type","errors","status_code","timestamp"],"description":"Every error from this service, whatever the cause. Rendered by InfrastructureError.","additionalProperties":true},"example":{"error_reference":"b7825cd3-59dc-43a9-9e1f-964676d3cf72","error_type":"NOT_FOUND_ERROR","errors":{"message":["Product with reference 11111111-1111-1111-1111-111111111111 not found."]},"status_code":404,"timestamp":"2026-09-30T10:00:00.000000"}}}},"409":{"description":"A record with that name already exists","content":{"application/json":{"schema":{"type":"object","properties":{"error_reference":{"type":"string","description":"Quote this when reporting a failure."},"error_type":{"type":"string"},"errors":{"type":"object","properties":{},"description":"Field name, or `message`, to a list of messages.","additionalProperties":true},"status_code":{"type":"integer"},"timestamp":{"type":"string"}},"required":["error_reference","error_type","errors","status_code","timestamp"],"description":"Every error from this service, whatever the cause. Rendered by InfrastructureError.","additionalProperties":true},"example":{"error_reference":"b7825cd3-59dc-43a9-9e1f-964676d3cf72","error_type":"NOT_FOUND_ERROR","errors":{"message":["Product with reference 11111111-1111-1111-1111-111111111111 not found."]},"status_code":409,"timestamp":"2026-09-30T10:00:00.000000"}}}},"500":{"description":"Unexpected failure. Quote `error_reference` when reporting it","content":{"application/json":{"schema":{"type":"object","properties":{"error_reference":{"type":"string","description":"Quote this when reporting a failure."},"error_type":{"type":"string"},"errors":{"type":"object","properties":{},"description":"Field name, or `message`, to a list of messages.","additionalProperties":true},"status_code":{"type":"integer"},"timestamp":{"type":"string"}},"required":["error_reference","error_type","errors","status_code","timestamp"],"description":"Every error from this service, whatever the cause. Rendered by InfrastructureError.","additionalProperties":true},"example":{"error_reference":"b7825cd3-59dc-43a9-9e1f-964676d3cf72","error_type":"NOT_FOUND_ERROR","errors":{"message":["Product with reference 11111111-1111-1111-1111-111111111111 not found."]},"status_code":500,"timestamp":"2026-09-30T10:00:00.000000"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}},"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"user_reference":{"type":["string","null"],"description":"Optional. Left out, an account with this email keeps its own reference, and somebody with no account is given a new one. Returned in the answer."},"email":{"type":"string","description":"The person's email. Required."},"organisation_reference":{"type":["string","null"]},"valid_from":{"type":["string","null"],"description":"YYYY-MM-DD. Defaults to today."},"entitlement":{"type":["object","null"],"description":"A hand-built grant. Required on /products/bundles/provision, which names no bundle. Must carry product_reference, the product it grants.","additionalProperties":true}},"additionalProperties":true},"example":{"email":"jane@example.com","organisation_reference":"77777777-7777-7777-7777-777777777777"}}}}}},"/products/bundles/{bundle_reference}/cart/custom":{"post":{"tags":["Cart"],"summary":"Add a custom bundle to the cart","description":"The caller picks the features, their months and the boxes. Every feature needs at least one of the selected boxes.","operationId":"add_product_to_cart_products_bundles__bundle_reference__cart_custom_post","security":[{"HTTPBearer":[]}],"parameters":[{"name":"bundle_reference","in":"path","required":true,"schema":{"type":"string","title":"Bundle Reference"}}],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"type":"object","properties":{"reference":{"type":"string"},"user_reference":{"type":"string","description":"Whose cart it went into."}},"required":["reference","user_reference"],"additionalProperties":true},"example":{"reference":"44444444-4444-4444-4444-444444444444","user_reference":"33333333-3333-3333-3333-333333333333"}}}},"400":{"description":"The body or a value in it is not valid","content":{"application/json":{"schema":{"type":"object","properties":{"error_reference":{"type":"string","description":"Quote this when reporting a failure."},"error_type":{"type":"string"},"errors":{"type":"object","properties":{},"description":"Field name, or `message`, to a list of messages.","additionalProperties":true},"status_code":{"type":"integer"},"timestamp":{"type":"string"}},"required":["error_reference","error_type","errors","status_code","timestamp"],"description":"Every error from this service, whatever the cause. Rendered by InfrastructureError.","additionalProperties":true},"example":{"error_reference":"b7825cd3-59dc-43a9-9e1f-964676d3cf72","error_type":"NOT_FOUND_ERROR","errors":{"message":["Product with reference 11111111-1111-1111-1111-111111111111 not found."]},"status_code":400,"timestamp":"2026-09-30T10:00:00.000000"}}}},"401":{"description":"No token, an expired token, or not allowed to do this","content":{"application/json":{"schema":{"type":"object","properties":{"error_reference":{"type":"string","description":"Quote this when reporting a failure."},"error_type":{"type":"string"},"errors":{"type":"object","properties":{},"description":"Field name, or `message`, to a list of messages.","additionalProperties":true},"status_code":{"type":"integer"},"timestamp":{"type":"string"}},"required":["error_reference","error_type","errors","status_code","timestamp"],"description":"Every error from this service, whatever the cause. Rendered by InfrastructureError.","additionalProperties":true},"example":{"error_reference":"b7825cd3-59dc-43a9-9e1f-964676d3cf72","error_type":"NOT_FOUND_ERROR","errors":{"message":["Product with reference 11111111-1111-1111-1111-111111111111 not found."]},"status_code":401,"timestamp":"2026-09-30T10:00:00.000000"}}}},"404":{"description":"No such record","content":{"application/json":{"schema":{"type":"object","properties":{"error_reference":{"type":"string","description":"Quote this when reporting a failure."},"error_type":{"type":"string"},"errors":{"type":"object","properties":{},"description":"Field name, or `message`, to a list of messages.","additionalProperties":true},"status_code":{"type":"integer"},"timestamp":{"type":"string"}},"required":["error_reference","error_type","errors","status_code","timestamp"],"description":"Every error from this service, whatever the cause. Rendered by InfrastructureError.","additionalProperties":true},"example":{"error_reference":"b7825cd3-59dc-43a9-9e1f-964676d3cf72","error_type":"NOT_FOUND_ERROR","errors":{"message":["Product with reference 11111111-1111-1111-1111-111111111111 not found."]},"status_code":404,"timestamp":"2026-09-30T10:00:00.000000"}}}},"500":{"description":"Unexpected failure. Quote `error_reference` when reporting it","content":{"application/json":{"schema":{"type":"object","properties":{"error_reference":{"type":"string","description":"Quote this when reporting a failure."},"error_type":{"type":"string"},"errors":{"type":"object","properties":{},"description":"Field name, or `message`, to a list of messages.","additionalProperties":true},"status_code":{"type":"integer"},"timestamp":{"type":"string"}},"required":["error_reference","error_type","errors","status_code","timestamp"],"description":"Every error from this service, whatever the cause. Rendered by InfrastructureError.","additionalProperties":true},"example":{"error_reference":"b7825cd3-59dc-43a9-9e1f-964676d3cf72","error_type":"NOT_FOUND_ERROR","errors":{"message":["Product with reference 11111111-1111-1111-1111-111111111111 not found."]},"status_code":500,"timestamp":"2026-09-30T10:00:00.000000"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}},"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"features":{"type":"array","items":{"type":"object","properties":{"feature_reference":{"type":"string"},"start_date":{"type":"string","description":"YYYY-MM-DD."},"months":{"type":"integer"},"one_off":{"type":"boolean"}},"required":["feature_reference","start_date","months","one_off"]}},"selected_boxes":{"type":"array","description":"Boxes picked from the features' own boxes.","items":{"type":"object","properties":{"lat_min":{"type":"number"},"lat_max":{"type":"number"},"lon_min":{"type":"number"},"lon_max":{"type":"number"},"coverage":{"type":"number"},"bath_value":{"type":"number"}},"additionalProperties":true}},"created_at_timestamp":{"type":"string","description":"Optional. Generated by the service when left out."},"target_email":{"type":"string","description":"Super administrators only: buy for somebody else. Ignored for anybody else."},"organisation_reference":{"type":"string","description":"Used with `target_email`."}},"required":["features","selected_boxes"]},"example":{"features":[{"feature_reference":"22222222-2222-2222-2222-222222222222","start_date":"2026-03-01","months":6,"one_off":false}],"selected_boxes":[{"lat_min":50.0,"lat_max":51.0,"lon_min":-2.0,"lon_max":-1.0,"coverage":100.0,"bath_value":10.0}]}}}}}},"/products/bundles/{bundle_reference}/cart":{"post":{"tags":["Cart"],"summary":"Add a fixed bundle to the cart","description":"The bundle decides the features, months and licence. Only the start date is taken from the body.","operationId":"add_product_bundle_to_cart_products_bundles__bundle_reference__cart_post","security":[{"HTTPBearer":[]}],"parameters":[{"name":"bundle_reference","in":"path","required":true,"schema":{"type":"string","title":"Bundle Reference"}}],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"type":"object","properties":{"reference":{"type":"string"},"user_reference":{"type":"string","description":"Whose cart it went into."}},"required":["reference","user_reference"],"additionalProperties":true},"example":{"reference":"44444444-4444-4444-4444-444444444444","user_reference":"33333333-3333-3333-3333-333333333333"}}}},"400":{"description":"The body or a value in it is not valid","content":{"application/json":{"schema":{"type":"object","properties":{"error_reference":{"type":"string","description":"Quote this when reporting a failure."},"error_type":{"type":"string"},"errors":{"type":"object","properties":{},"description":"Field name, or `message`, to a list of messages.","additionalProperties":true},"status_code":{"type":"integer"},"timestamp":{"type":"string"}},"required":["error_reference","error_type","errors","status_code","timestamp"],"description":"Every error from this service, whatever the cause. Rendered by InfrastructureError.","additionalProperties":true},"example":{"error_reference":"b7825cd3-59dc-43a9-9e1f-964676d3cf72","error_type":"NOT_FOUND_ERROR","errors":{"message":["Product with reference 11111111-1111-1111-1111-111111111111 not found."]},"status_code":400,"timestamp":"2026-09-30T10:00:00.000000"}}}},"401":{"description":"No token, an expired token, or not allowed to do this","content":{"application/json":{"schema":{"type":"object","properties":{"error_reference":{"type":"string","description":"Quote this when reporting a failure."},"error_type":{"type":"string"},"errors":{"type":"object","properties":{},"description":"Field name, or `message`, to a list of messages.","additionalProperties":true},"status_code":{"type":"integer"},"timestamp":{"type":"string"}},"required":["error_reference","error_type","errors","status_code","timestamp"],"description":"Every error from this service, whatever the cause. Rendered by InfrastructureError.","additionalProperties":true},"example":{"error_reference":"b7825cd3-59dc-43a9-9e1f-964676d3cf72","error_type":"NOT_FOUND_ERROR","errors":{"message":["Product with reference 11111111-1111-1111-1111-111111111111 not found."]},"status_code":401,"timestamp":"2026-09-30T10:00:00.000000"}}}},"404":{"description":"No such record","content":{"application/json":{"schema":{"type":"object","properties":{"error_reference":{"type":"string","description":"Quote this when reporting a failure."},"error_type":{"type":"string"},"errors":{"type":"object","properties":{},"description":"Field name, or `message`, to a list of messages.","additionalProperties":true},"status_code":{"type":"integer"},"timestamp":{"type":"string"}},"required":["error_reference","error_type","errors","status_code","timestamp"],"description":"Every error from this service, whatever the cause. Rendered by InfrastructureError.","additionalProperties":true},"example":{"error_reference":"b7825cd3-59dc-43a9-9e1f-964676d3cf72","error_type":"NOT_FOUND_ERROR","errors":{"message":["Product with reference 11111111-1111-1111-1111-111111111111 not found."]},"status_code":404,"timestamp":"2026-09-30T10:00:00.000000"}}}},"500":{"description":"Unexpected failure. Quote `error_reference` when reporting it","content":{"application/json":{"schema":{"type":"object","properties":{"error_reference":{"type":"string","description":"Quote this when reporting a failure."},"error_type":{"type":"string"},"errors":{"type":"object","properties":{},"description":"Field name, or `message`, to a list of messages.","additionalProperties":true},"status_code":{"type":"integer"},"timestamp":{"type":"string"}},"required":["error_reference","error_type","errors","status_code","timestamp"],"description":"Every error from this service, whatever the cause. Rendered by InfrastructureError.","additionalProperties":true},"example":{"error_reference":"b7825cd3-59dc-43a9-9e1f-964676d3cf72","error_type":"NOT_FOUND_ERROR","errors":{"message":["Product with reference 11111111-1111-1111-1111-111111111111 not found."]},"status_code":500,"timestamp":"2026-09-30T10:00:00.000000"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}},"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"start_date":{"type":"string","description":"YYYY-MM-DD."},"target_email":{"type":"string","description":"Super administrators only: buy for somebody else. Ignored for anybody else."},"organisation_reference":{"type":"string","description":"Used with `target_email`."}},"required":["start_date"],"additionalProperties":true},"example":{"start_date":"2026-03-01"}}}}}},"/products/bundles/{bundle_reference}":{"delete":{"tags":["Bundles"],"summary":"Delete a bundle","operationId":"delete_product_bundle_products_bundles__bundle_reference__delete","security":[{"HTTPBearer":[]}],"parameters":[{"name":"bundle_reference","in":"path","required":true,"schema":{"type":"string","title":"Bundle Reference"}}],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"type":"object","properties":{"reference":{"type":"string"},"message":{"type":"string"}},"required":["reference","message"],"additionalProperties":true},"example":{"reference":"44444444-4444-4444-4444-444444444444","message":"Feature 22222222-2222-2222-2222-222222222222 removed from bundle 44444444-4444-4444-4444-444444444444"}}}},"401":{"description":"No token, an expired token, or not allowed to do this","content":{"application/json":{"schema":{"type":"object","properties":{"error_reference":{"type":"string","description":"Quote this when reporting a failure."},"error_type":{"type":"string"},"errors":{"type":"object","properties":{},"description":"Field name, or `message`, to a list of messages.","additionalProperties":true},"status_code":{"type":"integer"},"timestamp":{"type":"string"}},"required":["error_reference","error_type","errors","status_code","timestamp"],"description":"Every error from this service, whatever the cause. Rendered by InfrastructureError.","additionalProperties":true},"example":{"error_reference":"b7825cd3-59dc-43a9-9e1f-964676d3cf72","error_type":"NOT_FOUND_ERROR","errors":{"message":["Product with reference 11111111-1111-1111-1111-111111111111 not found."]},"status_code":401,"timestamp":"2026-09-30T10:00:00.000000"}}}},"404":{"description":"No such record","content":{"application/json":{"schema":{"type":"object","properties":{"error_reference":{"type":"string","description":"Quote this when reporting a failure."},"error_type":{"type":"string"},"errors":{"type":"object","properties":{},"description":"Field name, or `message`, to a list of messages.","additionalProperties":true},"status_code":{"type":"integer"},"timestamp":{"type":"string"}},"required":["error_reference","error_type","errors","status_code","timestamp"],"description":"Every error from this service, whatever the cause. Rendered by InfrastructureError.","additionalProperties":true},"example":{"error_reference":"b7825cd3-59dc-43a9-9e1f-964676d3cf72","error_type":"NOT_FOUND_ERROR","errors":{"message":["Product with reference 11111111-1111-1111-1111-111111111111 not found."]},"status_code":404,"timestamp":"2026-09-30T10:00:00.000000"}}}},"500":{"description":"Unexpected failure. Quote `error_reference` when reporting it","content":{"application/json":{"schema":{"type":"object","properties":{"error_reference":{"type":"string","description":"Quote this when reporting a failure."},"error_type":{"type":"string"},"errors":{"type":"object","properties":{},"description":"Field name, or `message`, to a list of messages.","additionalProperties":true},"status_code":{"type":"integer"},"timestamp":{"type":"string"}},"required":["error_reference","error_type","errors","status_code","timestamp"],"description":"Every error from this service, whatever the cause. Rendered by InfrastructureError.","additionalProperties":true},"example":{"error_reference":"b7825cd3-59dc-43a9-9e1f-964676d3cf72","error_type":"NOT_FOUND_ERROR","errors":{"message":["Product with reference 11111111-1111-1111-1111-111111111111 not found."]},"status_code":500,"timestamp":"2026-09-30T10:00:00.000000"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/products/bundles":{"get":{"tags":["Bundles"],"summary":"List bundles","description":"Paged, ten to a page. Any field of the record can be used as a filter\n(`?field=value`), not only the ones listed; text is matched partially and case-insensitively.\n`all` searches across the text fields instead. `ordering=field` sorts ascending,\n`ordering=-field` descending; the default is newest first.","operationId":"get_bundles_products_bundles_get","parameters":[{"name":"page","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Page number. `all` turns paging off.","examples":["1"],"title":"Page"},"description":"Page number. `all` turns paging off."},{"name":"ordering","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"`field` ascending, `-field` descending. Default `-created_at_timestamp`.","examples":["bundle_name"],"title":"Ordering"},"description":"`field` ascending, `-field` descending. Default `-created_at_timestamp`."},{"name":"all","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Search across the text fields.","title":"All"},"description":"Search across the text fields."},{"name":"bundle_name","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Partial, case-insensitive.","title":"Bundle Name"},"description":"Partial, case-insensitive."},{"name":"bundle_type","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"For example `user` or `organisation`.","title":"Bundle Type"},"description":"For example `user` or `organisation`."},{"name":"product_reference","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"The product the bundles belong to.","title":"Product Reference"},"description":"The product the bundles belong to."}],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"type":"object","properties":{"items":{"type":"array","items":{"type":"object","properties":{"reference":{"type":"string"},"product_reference":{"type":"string"},"product":{"type":"object","description":"The product's name and description, copied onto the bundle.","additionalProperties":true},"bundle_name":{"type":"string"},"bundle_type":{"type":"string","description":"For example `user` or `organisation`."},"description":{"type":["string","null"]},"base_price":{"type":"number"},"license":{"type":"object","description":"The licence record the bundle carries, or `{}`.","additionalProperties":true},"features":{"type":"array","description":"Feature records, each with the `selected_boxes` that belong to it.","items":{"type":"object","additionalProperties":true}},"selected_boxes":{"type":"array","items":{"type":"object","properties":{"lat_min":{"type":"number"},"lat_max":{"type":"number"},"lon_min":{"type":"number"},"lon_max":{"type":"number"},"coverage":{"type":"number"},"bath_value":{"type":"number"}},"additionalProperties":true}},"discount":{"type":"object","description":"The discount record the bundle carries, or `{}`.","additionalProperties":true},"total_price":{"type":"number"},"metadata":{"type":"object","additionalProperties":true},"created_by_reference":{"type":["string","null"]},"updated_by_reference":{"type":["string","null"]},"created_at_timestamp":{"type":["string","null"]},"updated_at_timestamp":{"type":["string","null"]},"original_price_before_discount":{"type":"number"},"applied_discount_value":{"type":"number"},"final_price_after_discount":{"type":"number"}},"required":["reference","product_reference","product","bundle_name","bundle_type","description","base_price","license","features","selected_boxes","discount","total_price","metadata","created_by_reference","updated_by_reference","created_at_timestamp","updated_at_timestamp","original_price_before_discount","applied_discount_value","final_price_after_discount"],"description":"A bundle, as `Bundle.to_dict` renders it, with its price worked out.","additionalProperties":true}},"pagination":{"type":"object","properties":{"current_page":{"type":"integer"},"page_size":{"type":"integer"},"total_records":{"type":"integer"},"total_pages":{"type":"integer"},"has_next":{"type":"boolean"},"has_previous":{"type":"boolean"}},"required":["current_page","page_size","total_records","total_pages","has_next","has_previous"],"additionalProperties":true}},"required":["items","pagination"],"additionalProperties":true},"example":{"items":[{"reference":"44444444-4444-4444-4444-444444444444","product_reference":"11111111-1111-1111-1111-111111111111","product":{"product_name":"Ports","description":"Port prediction product"},"bundle_name":"Premium ports","bundle_type":"user","description":"A premium ports bundle","base_price":100.0,"license":{"reference":"55555555-5555-5555-5555-555555555555","license_type":"Premium","max_users":5,"multiplier":1.0,"duration":12,"metadata":{}},"features":[{"reference":"22222222-2222-2222-2222-222222222222","feature_name":"Tidal streams","description":"A catalogue feature","feature_type":"model","base_price":100.0,"prediction":1,"hindcast":1,"box_size":"1x1","price_multiplier":1.0,"min_boxes":1,"boxes":[{"lat_min":50.0,"lat_max":51.0,"lon_min":-2.0,"lon_max":-1.0,"coverage":100.0,"bath_value":10.0}],"total_price":100.0,"metadata":{},"created_by_reference":"33333333-3333-3333-3333-333333333333","updated_by_reference":"33333333-3333-3333-3333-333333333333","created_at_timestamp":"2026-01-01T09:30:00","updated_at_timestamp":"2026-01-01T09:30:00","selected_boxes":[{"lat_min":50.0,"lat_max":51.0,"lon_min":-2.0,"lon_max":-1.0,"coverage":100.0,"bath_value":10.0}]}],"selected_boxes":[{"lat_min":50.0,"lat_max":51.0,"lon_min":-2.0,"lon_max":-1.0,"coverage":100.0,"bath_value":10.0}],"discount":{},"total_price":200.0,"metadata":{},"created_by_reference":"33333333-3333-3333-3333-333333333333","updated_by_reference":"33333333-3333-3333-3333-333333333333","created_at_timestamp":"2026-01-01T09:30:00","updated_at_timestamp":"2026-01-01T09:30:00","original_price_before_discount":200.0,"applied_discount_value":0.0,"final_price_after_discount":200.0}],"pagination":{"current_page":1,"page_size":10,"total_records":1,"total_pages":1,"has_next":false,"has_previous":false}}}}},"500":{"description":"Unexpected failure. Quote `error_reference` when reporting it","content":{"application/json":{"schema":{"type":"object","properties":{"error_reference":{"type":"string","description":"Quote this when reporting a failure."},"error_type":{"type":"string"},"errors":{"type":"object","properties":{},"description":"Field name, or `message`, to a list of messages.","additionalProperties":true},"status_code":{"type":"integer"},"timestamp":{"type":"string"}},"required":["error_reference","error_type","errors","status_code","timestamp"],"description":"Every error from this service, whatever the cause. Rendered by InfrastructureError.","additionalProperties":true},"example":{"error_reference":"b7825cd3-59dc-43a9-9e1f-964676d3cf72","error_type":"NOT_FOUND_ERROR","errors":{"message":["Product with reference 11111111-1111-1111-1111-111111111111 not found."]},"status_code":500,"timestamp":"2026-09-30T10:00:00.000000"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/products/discounts/{discount_reference}":{"delete":{"tags":["Discounts"],"summary":"Delete a discount","description":"Also takes it off every bundle that carries it.","operationId":"delete_discount_products_discounts__discount_reference__delete","security":[{"HTTPBearer":[]}],"parameters":[{"name":"discount_reference","in":"path","required":true,"schema":{"type":"string","title":"Discount Reference"}}],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"type":"object","properties":{"reference":{"type":"string"}},"required":["reference"],"description":"The reference of the record that was created, changed or removed.","additionalProperties":true},"example":{"reference":"11111111-1111-1111-1111-111111111111"}}}},"401":{"description":"No token, an expired token, or not allowed to do this","content":{"application/json":{"schema":{"type":"object","properties":{"error_reference":{"type":"string","description":"Quote this when reporting a failure."},"error_type":{"type":"string"},"errors":{"type":"object","properties":{},"description":"Field name, or `message`, to a list of messages.","additionalProperties":true},"status_code":{"type":"integer"},"timestamp":{"type":"string"}},"required":["error_reference","error_type","errors","status_code","timestamp"],"description":"Every error from this service, whatever the cause. Rendered by InfrastructureError.","additionalProperties":true},"example":{"error_reference":"b7825cd3-59dc-43a9-9e1f-964676d3cf72","error_type":"NOT_FOUND_ERROR","errors":{"message":["Product with reference 11111111-1111-1111-1111-111111111111 not found."]},"status_code":401,"timestamp":"2026-09-30T10:00:00.000000"}}}},"404":{"description":"No such record","content":{"application/json":{"schema":{"type":"object","properties":{"error_reference":{"type":"string","description":"Quote this when reporting a failure."},"error_type":{"type":"string"},"errors":{"type":"object","properties":{},"description":"Field name, or `message`, to a list of messages.","additionalProperties":true},"status_code":{"type":"integer"},"timestamp":{"type":"string"}},"required":["error_reference","error_type","errors","status_code","timestamp"],"description":"Every error from this service, whatever the cause. Rendered by InfrastructureError.","additionalProperties":true},"example":{"error_reference":"b7825cd3-59dc-43a9-9e1f-964676d3cf72","error_type":"NOT_FOUND_ERROR","errors":{"message":["Product with reference 11111111-1111-1111-1111-111111111111 not found."]},"status_code":404,"timestamp":"2026-09-30T10:00:00.000000"}}}},"500":{"description":"Unexpected failure. Quote `error_reference` when reporting it","content":{"application/json":{"schema":{"type":"object","properties":{"error_reference":{"type":"string","description":"Quote this when reporting a failure."},"error_type":{"type":"string"},"errors":{"type":"object","properties":{},"description":"Field name, or `message`, to a list of messages.","additionalProperties":true},"status_code":{"type":"integer"},"timestamp":{"type":"string"}},"required":["error_reference","error_type","errors","status_code","timestamp"],"description":"Every error from this service, whatever the cause. Rendered by InfrastructureError.","additionalProperties":true},"example":{"error_reference":"b7825cd3-59dc-43a9-9e1f-964676d3cf72","error_type":"NOT_FOUND_ERROR","errors":{"message":["Product with reference 11111111-1111-1111-1111-111111111111 not found."]},"status_code":500,"timestamp":"2026-09-30T10:00:00.000000"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"put":{"tags":["Discounts"],"summary":"Update a discount","description":"Partial: send only what changes. Bundles carrying it get the new version.","operationId":"update_discount_products_discounts__discount_reference__put","security":[{"HTTPBearer":[]}],"parameters":[{"name":"discount_reference","in":"path","required":true,"schema":{"type":"string","title":"Discount Reference"}}],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"type":"object","properties":{"reference":{"type":"string"}},"required":["reference"],"description":"The reference of the record that was created, changed or removed.","additionalProperties":true},"example":{"reference":"11111111-1111-1111-1111-111111111111"}}}},"400":{"description":"The body or a value in it is not valid","content":{"application/json":{"schema":{"type":"object","properties":{"error_reference":{"type":"string","description":"Quote this when reporting a failure."},"error_type":{"type":"string"},"errors":{"type":"object","properties":{},"description":"Field name, or `message`, to a list of messages.","additionalProperties":true},"status_code":{"type":"integer"},"timestamp":{"type":"string"}},"required":["error_reference","error_type","errors","status_code","timestamp"],"description":"Every error from this service, whatever the cause. Rendered by InfrastructureError.","additionalProperties":true},"example":{"error_reference":"b7825cd3-59dc-43a9-9e1f-964676d3cf72","error_type":"NOT_FOUND_ERROR","errors":{"message":["Product with reference 11111111-1111-1111-1111-111111111111 not found."]},"status_code":400,"timestamp":"2026-09-30T10:00:00.000000"}}}},"401":{"description":"No token, an expired token, or not allowed to do this","content":{"application/json":{"schema":{"type":"object","properties":{"error_reference":{"type":"string","description":"Quote this when reporting a failure."},"error_type":{"type":"string"},"errors":{"type":"object","properties":{},"description":"Field name, or `message`, to a list of messages.","additionalProperties":true},"status_code":{"type":"integer"},"timestamp":{"type":"string"}},"required":["error_reference","error_type","errors","status_code","timestamp"],"description":"Every error from this service, whatever the cause. Rendered by InfrastructureError.","additionalProperties":true},"example":{"error_reference":"b7825cd3-59dc-43a9-9e1f-964676d3cf72","error_type":"NOT_FOUND_ERROR","errors":{"message":["Product with reference 11111111-1111-1111-1111-111111111111 not found."]},"status_code":401,"timestamp":"2026-09-30T10:00:00.000000"}}}},"404":{"description":"No such record","content":{"application/json":{"schema":{"type":"object","properties":{"error_reference":{"type":"string","description":"Quote this when reporting a failure."},"error_type":{"type":"string"},"errors":{"type":"object","properties":{},"description":"Field name, or `message`, to a list of messages.","additionalProperties":true},"status_code":{"type":"integer"},"timestamp":{"type":"string"}},"required":["error_reference","error_type","errors","status_code","timestamp"],"description":"Every error from this service, whatever the cause. Rendered by InfrastructureError.","additionalProperties":true},"example":{"error_reference":"b7825cd3-59dc-43a9-9e1f-964676d3cf72","error_type":"NOT_FOUND_ERROR","errors":{"message":["Product with reference 11111111-1111-1111-1111-111111111111 not found."]},"status_code":404,"timestamp":"2026-09-30T10:00:00.000000"}}}},"409":{"description":"A record with that name already exists","content":{"application/json":{"schema":{"type":"object","properties":{"error_reference":{"type":"string","description":"Quote this when reporting a failure."},"error_type":{"type":"string"},"errors":{"type":"object","properties":{},"description":"Field name, or `message`, to a list of messages.","additionalProperties":true},"status_code":{"type":"integer"},"timestamp":{"type":"string"}},"required":["error_reference","error_type","errors","status_code","timestamp"],"description":"Every error from this service, whatever the cause. Rendered by InfrastructureError.","additionalProperties":true},"example":{"error_reference":"b7825cd3-59dc-43a9-9e1f-964676d3cf72","error_type":"NOT_FOUND_ERROR","errors":{"message":["Product with reference 11111111-1111-1111-1111-111111111111 not found."]},"status_code":409,"timestamp":"2026-09-30T10:00:00.000000"}}}},"500":{"description":"Unexpected failure. Quote `error_reference` when reporting it","content":{"application/json":{"schema":{"type":"object","properties":{"error_reference":{"type":"string","description":"Quote this when reporting a failure."},"error_type":{"type":"string"},"errors":{"type":"object","properties":{},"description":"Field name, or `message`, to a list of messages.","additionalProperties":true},"status_code":{"type":"integer"},"timestamp":{"type":"string"}},"required":["error_reference","error_type","errors","status_code","timestamp"],"description":"Every error from this service, whatever the cause. Rendered by InfrastructureError.","additionalProperties":true},"example":{"error_reference":"b7825cd3-59dc-43a9-9e1f-964676d3cf72","error_type":"NOT_FOUND_ERROR","errors":{"message":["Product with reference 11111111-1111-1111-1111-111111111111 not found."]},"status_code":500,"timestamp":"2026-09-30T10:00:00.000000"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}},"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"discount_name":{"type":["string","null"]},"percentage":{"type":["boolean","null"]},"value":{"type":["number","null"]},"valid_from":{"type":["string","null"]},"valid_to":{"type":["string","null"]},"usage_limit":{"type":["integer","null"]},"minimum_purchase":{"type":["number","null"]},"num_promo_codes":{"type":["integer","null"],"description":"Above 0 replaces the promo codes with this many new ones."}},"description":"Send only what changes."},"example":{"discount_name":"Spring offer"}}}}},"get":{"tags":["Discounts"],"summary":"One discount","operationId":"get_discount_by_reference_products_discounts__discount_reference__get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"discount_reference","in":"path","required":true,"schema":{"type":"string","title":"Discount Reference"}}],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"type":"object","properties":{"reference":{"type":"string"},"discount_name":{"type":"string"},"percentage":{"type":"boolean","description":"True when `value` is a percentage, false when it is an amount."},"value":{"type":"number"},"promo_codes":{"type":"array","items":{"type":"string"}},"valid_from":{"type":"string"},"valid_to":{"type":"string"},"usage_limit":{"type":"integer"},"minimum_purchase":{"type":"number"}},"required":["reference","discount_name","percentage","value","promo_codes","valid_from","valid_to","usage_limit","minimum_purchase"],"description":"A discount, as `Discount.to_dict` renders it.","additionalProperties":true},"example":{"reference":"66666666-6666-6666-6666-666666666666","discount_name":"Launch offer","percentage":true,"value":10.0,"promo_codes":["D477F4FB","F06C58B3"],"valid_from":"2026-01-01","valid_to":"2026-12-31","usage_limit":100,"minimum_purchase":0.0}}}},"401":{"description":"No token, an expired token, or not allowed to do this","content":{"application/json":{"schema":{"type":"object","properties":{"error_reference":{"type":"string","description":"Quote this when reporting a failure."},"error_type":{"type":"string"},"errors":{"type":"object","properties":{},"description":"Field name, or `message`, to a list of messages.","additionalProperties":true},"status_code":{"type":"integer"},"timestamp":{"type":"string"}},"required":["error_reference","error_type","errors","status_code","timestamp"],"description":"Every error from this service, whatever the cause. Rendered by InfrastructureError.","additionalProperties":true},"example":{"error_reference":"b7825cd3-59dc-43a9-9e1f-964676d3cf72","error_type":"NOT_FOUND_ERROR","errors":{"message":["Product with reference 11111111-1111-1111-1111-111111111111 not found."]},"status_code":401,"timestamp":"2026-09-30T10:00:00.000000"}}}},"404":{"description":"No such record","content":{"application/json":{"schema":{"type":"object","properties":{"error_reference":{"type":"string","description":"Quote this when reporting a failure."},"error_type":{"type":"string"},"errors":{"type":"object","properties":{},"description":"Field name, or `message`, to a list of messages.","additionalProperties":true},"status_code":{"type":"integer"},"timestamp":{"type":"string"}},"required":["error_reference","error_type","errors","status_code","timestamp"],"description":"Every error from this service, whatever the cause. Rendered by InfrastructureError.","additionalProperties":true},"example":{"error_reference":"b7825cd3-59dc-43a9-9e1f-964676d3cf72","error_type":"NOT_FOUND_ERROR","errors":{"message":["Product with reference 11111111-1111-1111-1111-111111111111 not found."]},"status_code":404,"timestamp":"2026-09-30T10:00:00.000000"}}}},"500":{"description":"Unexpected failure. Quote `error_reference` when reporting it","content":{"application/json":{"schema":{"type":"object","properties":{"error_reference":{"type":"string","description":"Quote this when reporting a failure."},"error_type":{"type":"string"},"errors":{"type":"object","properties":{},"description":"Field name, or `message`, to a list of messages.","additionalProperties":true},"status_code":{"type":"integer"},"timestamp":{"type":"string"}},"required":["error_reference","error_type","errors","status_code","timestamp"],"description":"Every error from this service, whatever the cause. Rendered by InfrastructureError.","additionalProperties":true},"example":{"error_reference":"b7825cd3-59dc-43a9-9e1f-964676d3cf72","error_type":"NOT_FOUND_ERROR","errors":{"message":["Product with reference 11111111-1111-1111-1111-111111111111 not found."]},"status_code":500,"timestamp":"2026-09-30T10:00:00.000000"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/products/discounts":{"post":{"tags":["Discounts"],"summary":"Create a discount","description":"The name must be unique. `num_promo_codes` codes are generated.","operationId":"create_discount_products_discounts_post","security":[{"HTTPBearer":[]}],"responses":{"201":{"description":"Created","content":{"application/json":{"schema":{"type":"object","properties":{"reference":{"type":"string"}},"required":["reference"],"description":"The reference of the record that was created, changed or removed.","additionalProperties":true},"example":{"reference":"11111111-1111-1111-1111-111111111111"}}}},"400":{"description":"The body or a value in it is not valid","content":{"application/json":{"schema":{"type":"object","properties":{"error_reference":{"type":"string","description":"Quote this when reporting a failure."},"error_type":{"type":"string"},"errors":{"type":"object","properties":{},"description":"Field name, or `message`, to a list of messages.","additionalProperties":true},"status_code":{"type":"integer"},"timestamp":{"type":"string"}},"required":["error_reference","error_type","errors","status_code","timestamp"],"description":"Every error from this service, whatever the cause. Rendered by InfrastructureError.","additionalProperties":true},"example":{"error_reference":"b7825cd3-59dc-43a9-9e1f-964676d3cf72","error_type":"NOT_FOUND_ERROR","errors":{"message":["Product with reference 11111111-1111-1111-1111-111111111111 not found."]},"status_code":400,"timestamp":"2026-09-30T10:00:00.000000"}}}},"401":{"description":"No token, an expired token, or not allowed to do this","content":{"application/json":{"schema":{"type":"object","properties":{"error_reference":{"type":"string","description":"Quote this when reporting a failure."},"error_type":{"type":"string"},"errors":{"type":"object","properties":{},"description":"Field name, or `message`, to a list of messages.","additionalProperties":true},"status_code":{"type":"integer"},"timestamp":{"type":"string"}},"required":["error_reference","error_type","errors","status_code","timestamp"],"description":"Every error from this service, whatever the cause. Rendered by InfrastructureError.","additionalProperties":true},"example":{"error_reference":"b7825cd3-59dc-43a9-9e1f-964676d3cf72","error_type":"NOT_FOUND_ERROR","errors":{"message":["Product with reference 11111111-1111-1111-1111-111111111111 not found."]},"status_code":401,"timestamp":"2026-09-30T10:00:00.000000"}}}},"409":{"description":"A record with that name already exists","content":{"application/json":{"schema":{"type":"object","properties":{"error_reference":{"type":"string","description":"Quote this when reporting a failure."},"error_type":{"type":"string"},"errors":{"type":"object","properties":{},"description":"Field name, or `message`, to a list of messages.","additionalProperties":true},"status_code":{"type":"integer"},"timestamp":{"type":"string"}},"required":["error_reference","error_type","errors","status_code","timestamp"],"description":"Every error from this service, whatever the cause. Rendered by InfrastructureError.","additionalProperties":true},"example":{"error_reference":"b7825cd3-59dc-43a9-9e1f-964676d3cf72","error_type":"NOT_FOUND_ERROR","errors":{"message":["Product with reference 11111111-1111-1111-1111-111111111111 not found."]},"status_code":409,"timestamp":"2026-09-30T10:00:00.000000"}}}},"500":{"description":"Unexpected failure. Quote `error_reference` when reporting it","content":{"application/json":{"schema":{"type":"object","properties":{"error_reference":{"type":"string","description":"Quote this when reporting a failure."},"error_type":{"type":"string"},"errors":{"type":"object","properties":{},"description":"Field name, or `message`, to a list of messages.","additionalProperties":true},"status_code":{"type":"integer"},"timestamp":{"type":"string"}},"required":["error_reference","error_type","errors","status_code","timestamp"],"description":"Every error from this service, whatever the cause. Rendered by InfrastructureError.","additionalProperties":true},"example":{"error_reference":"b7825cd3-59dc-43a9-9e1f-964676d3cf72","error_type":"NOT_FOUND_ERROR","errors":{"message":["Product with reference 11111111-1111-1111-1111-111111111111 not found."]},"status_code":500,"timestamp":"2026-09-30T10:00:00.000000"}}}}},"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"discount_name":{"type":"string"},"percentage":{"type":"boolean","description":"True when `value` is a percentage, false when it is an amount."},"value":{"type":"number"},"valid_from":{"type":"string","description":"YYYY-MM-DD."},"valid_to":{"type":"string","description":"YYYY-MM-DD."},"usage_limit":{"type":"integer"},"minimum_purchase":{"type":"number"},"num_promo_codes":{"type":"integer","description":"How many promo codes to generate."},"reference":{"type":"string","description":"Optional. Generated by the service when left out."}},"required":["discount_name","percentage","value","valid_from","valid_to","usage_limit","minimum_purchase","num_promo_codes"]},"example":{"discount_name":"Launch offer","percentage":true,"value":10.0,"valid_from":"2026-10-01","valid_to":"2026-12-31","usage_limit":100,"minimum_purchase":0.0,"num_promo_codes":2}}}}},"get":{"tags":["Discounts"],"summary":"List discounts","description":"Paged, ten to a page. Any field of the record can be used as a filter\n(`?field=value`), not only the ones listed; text is matched partially and case-insensitively.\n`all` searches across the text fields instead. `ordering=field` sorts ascending,\n`ordering=-field` descending; the default is newest first.","operationId":"get_discounts_products_discounts_get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"page","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Page number. `all` turns paging off.","examples":["1"],"title":"Page"},"description":"Page number. `all` turns paging off."},{"name":"ordering","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"`field` ascending, `-field` descending. Default `-created_at_timestamp`.","examples":["discount_name"],"title":"Ordering"},"description":"`field` ascending, `-field` descending. Default `-created_at_timestamp`."},{"name":"all","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Search across the text fields.","title":"All"},"description":"Search across the text fields."},{"name":"discount_name","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Partial, case-insensitive.","title":"Discount Name"},"description":"Partial, case-insensitive."}],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"type":"object","properties":{"items":{"type":"array","items":{"type":"object","properties":{"reference":{"type":"string"},"discount_name":{"type":"string"},"percentage":{"type":"boolean","description":"True when `value` is a percentage, false when it is an amount."},"value":{"type":"number"},"promo_codes":{"type":"array","items":{"type":"string"}},"valid_from":{"type":"string"},"valid_to":{"type":"string"},"usage_limit":{"type":"integer"},"minimum_purchase":{"type":"number"}},"required":["reference","discount_name","percentage","value","promo_codes","valid_from","valid_to","usage_limit","minimum_purchase"],"description":"A discount, as `Discount.to_dict` renders it.","additionalProperties":true}},"pagination":{"type":"object","properties":{"current_page":{"type":"integer"},"page_size":{"type":"integer"},"total_records":{"type":"integer"},"total_pages":{"type":"integer"},"has_next":{"type":"boolean"},"has_previous":{"type":"boolean"}},"required":["current_page","page_size","total_records","total_pages","has_next","has_previous"],"additionalProperties":true}},"required":["items","pagination"],"additionalProperties":true},"example":{"items":[{"reference":"66666666-6666-6666-6666-666666666666","discount_name":"Launch offer","percentage":true,"value":10.0,"promo_codes":["D477F4FB","F06C58B3"],"valid_from":"2026-01-01","valid_to":"2026-12-31","usage_limit":100,"minimum_purchase":0.0}],"pagination":{"current_page":1,"page_size":10,"total_records":1,"total_pages":1,"has_next":false,"has_previous":false}}}}},"401":{"description":"No token, an expired token, or not allowed to do this","content":{"application/json":{"schema":{"type":"object","properties":{"error_reference":{"type":"string","description":"Quote this when reporting a failure."},"error_type":{"type":"string"},"errors":{"type":"object","properties":{},"description":"Field name, or `message`, to a list of messages.","additionalProperties":true},"status_code":{"type":"integer"},"timestamp":{"type":"string"}},"required":["error_reference","error_type","errors","status_code","timestamp"],"description":"Every error from this service, whatever the cause. Rendered by InfrastructureError.","additionalProperties":true},"example":{"error_reference":"b7825cd3-59dc-43a9-9e1f-964676d3cf72","error_type":"NOT_FOUND_ERROR","errors":{"message":["Product with reference 11111111-1111-1111-1111-111111111111 not found."]},"status_code":401,"timestamp":"2026-09-30T10:00:00.000000"}}}},"500":{"description":"Unexpected failure. Quote `error_reference` when reporting it","content":{"application/json":{"schema":{"type":"object","properties":{"error_reference":{"type":"string","description":"Quote this when reporting a failure."},"error_type":{"type":"string"},"errors":{"type":"object","properties":{},"description":"Field name, or `message`, to a list of messages.","additionalProperties":true},"status_code":{"type":"integer"},"timestamp":{"type":"string"}},"required":["error_reference","error_type","errors","status_code","timestamp"],"description":"Every error from this service, whatever the cause. Rendered by InfrastructureError.","additionalProperties":true},"example":{"error_reference":"b7825cd3-59dc-43a9-9e1f-964676d3cf72","error_type":"NOT_FOUND_ERROR","errors":{"message":["Product with reference 11111111-1111-1111-1111-111111111111 not found."]},"status_code":500,"timestamp":"2026-09-30T10:00:00.000000"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/products/licenses/{license_reference}":{"get":{"tags":["Licenses"],"summary":"One licence type","operationId":"get_license_by_reference_products_licenses__license_reference__get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"license_reference","in":"path","required":true,"schema":{"type":"string","title":"License Reference"}}],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"type":"object","properties":{"reference":{"type":"string"},"license_type":{"type":"string"},"max_users":{"type":"integer"},"multiplier":{"type":"number"},"duration":{"type":"integer","description":"Months."},"metadata":{"type":"object","additionalProperties":true}},"required":["reference","license_type","max_users","multiplier","duration","metadata"],"description":"A licence type, as `License.to_dict` renders it.","additionalProperties":true},"example":{"reference":"55555555-5555-5555-5555-555555555555","license_type":"Premium","max_users":5,"multiplier":1.0,"duration":12,"metadata":{}}}}},"401":{"description":"No token, an expired token, or not allowed to do this","content":{"application/json":{"schema":{"type":"object","properties":{"error_reference":{"type":"string","description":"Quote this when reporting a failure."},"error_type":{"type":"string"},"errors":{"type":"object","properties":{},"description":"Field name, or `message`, to a list of messages.","additionalProperties":true},"status_code":{"type":"integer"},"timestamp":{"type":"string"}},"required":["error_reference","error_type","errors","status_code","timestamp"],"description":"Every error from this service, whatever the cause. Rendered by InfrastructureError.","additionalProperties":true},"example":{"error_reference":"b7825cd3-59dc-43a9-9e1f-964676d3cf72","error_type":"NOT_FOUND_ERROR","errors":{"message":["Product with reference 11111111-1111-1111-1111-111111111111 not found."]},"status_code":401,"timestamp":"2026-09-30T10:00:00.000000"}}}},"404":{"description":"No such record","content":{"application/json":{"schema":{"type":"object","properties":{"error_reference":{"type":"string","description":"Quote this when reporting a failure."},"error_type":{"type":"string"},"errors":{"type":"object","properties":{},"description":"Field name, or `message`, to a list of messages.","additionalProperties":true},"status_code":{"type":"integer"},"timestamp":{"type":"string"}},"required":["error_reference","error_type","errors","status_code","timestamp"],"description":"Every error from this service, whatever the cause. Rendered by InfrastructureError.","additionalProperties":true},"example":{"error_reference":"b7825cd3-59dc-43a9-9e1f-964676d3cf72","error_type":"NOT_FOUND_ERROR","errors":{"message":["Product with reference 11111111-1111-1111-1111-111111111111 not found."]},"status_code":404,"timestamp":"2026-09-30T10:00:00.000000"}}}},"500":{"description":"Unexpected failure. Quote `error_reference` when reporting it","content":{"application/json":{"schema":{"type":"object","properties":{"error_reference":{"type":"string","description":"Quote this when reporting a failure."},"error_type":{"type":"string"},"errors":{"type":"object","properties":{},"description":"Field name, or `message`, to a list of messages.","additionalProperties":true},"status_code":{"type":"integer"},"timestamp":{"type":"string"}},"required":["error_reference","error_type","errors","status_code","timestamp"],"description":"Every error from this service, whatever the cause. Rendered by InfrastructureError.","additionalProperties":true},"example":{"error_reference":"b7825cd3-59dc-43a9-9e1f-964676d3cf72","error_type":"NOT_FOUND_ERROR","errors":{"message":["Product with reference 11111111-1111-1111-1111-111111111111 not found."]},"status_code":500,"timestamp":"2026-09-30T10:00:00.000000"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"delete":{"tags":["Licenses"],"summary":"Delete a licence type","operationId":"delete_license_products_licenses__license_reference__delete","security":[{"HTTPBearer":[]}],"parameters":[{"name":"license_reference","in":"path","required":true,"schema":{"type":"string","title":"License Reference"}}],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"type":"object","properties":{"reference":{"type":"string"},"message":{"type":"string"}},"required":["reference","message"],"additionalProperties":true},"example":{"reference":"44444444-4444-4444-4444-444444444444","message":"Feature 22222222-2222-2222-2222-222222222222 removed from bundle 44444444-4444-4444-4444-444444444444"}}}},"401":{"description":"No token, an expired token, or not allowed to do this","content":{"application/json":{"schema":{"type":"object","properties":{"error_reference":{"type":"string","description":"Quote this when reporting a failure."},"error_type":{"type":"string"},"errors":{"type":"object","properties":{},"description":"Field name, or `message`, to a list of messages.","additionalProperties":true},"status_code":{"type":"integer"},"timestamp":{"type":"string"}},"required":["error_reference","error_type","errors","status_code","timestamp"],"description":"Every error from this service, whatever the cause. Rendered by InfrastructureError.","additionalProperties":true},"example":{"error_reference":"b7825cd3-59dc-43a9-9e1f-964676d3cf72","error_type":"NOT_FOUND_ERROR","errors":{"message":["Product with reference 11111111-1111-1111-1111-111111111111 not found."]},"status_code":401,"timestamp":"2026-09-30T10:00:00.000000"}}}},"404":{"description":"No such record","content":{"application/json":{"schema":{"type":"object","properties":{"error_reference":{"type":"string","description":"Quote this when reporting a failure."},"error_type":{"type":"string"},"errors":{"type":"object","properties":{},"description":"Field name, or `message`, to a list of messages.","additionalProperties":true},"status_code":{"type":"integer"},"timestamp":{"type":"string"}},"required":["error_reference","error_type","errors","status_code","timestamp"],"description":"Every error from this service, whatever the cause. Rendered by InfrastructureError.","additionalProperties":true},"example":{"error_reference":"b7825cd3-59dc-43a9-9e1f-964676d3cf72","error_type":"NOT_FOUND_ERROR","errors":{"message":["Product with reference 11111111-1111-1111-1111-111111111111 not found."]},"status_code":404,"timestamp":"2026-09-30T10:00:00.000000"}}}},"500":{"description":"Unexpected failure. Quote `error_reference` when reporting it","content":{"application/json":{"schema":{"type":"object","properties":{"error_reference":{"type":"string","description":"Quote this when reporting a failure."},"error_type":{"type":"string"},"errors":{"type":"object","properties":{},"description":"Field name, or `message`, to a list of messages.","additionalProperties":true},"status_code":{"type":"integer"},"timestamp":{"type":"string"}},"required":["error_reference","error_type","errors","status_code","timestamp"],"description":"Every error from this service, whatever the cause. Rendered by InfrastructureError.","additionalProperties":true},"example":{"error_reference":"b7825cd3-59dc-43a9-9e1f-964676d3cf72","error_type":"NOT_FOUND_ERROR","errors":{"message":["Product with reference 11111111-1111-1111-1111-111111111111 not found."]},"status_code":500,"timestamp":"2026-09-30T10:00:00.000000"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"put":{"tags":["Licenses"],"summary":"Update a licence type","operationId":"update_license_products_licenses__license_reference__put","security":[{"HTTPBearer":[]}],"parameters":[{"name":"license_reference","in":"path","required":true,"schema":{"type":"string","title":"License Reference"}}],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"type":"object","properties":{"reference":{"type":"string"}},"required":["reference"],"description":"The reference of the record that was created, changed or removed.","additionalProperties":true},"example":{"reference":"11111111-1111-1111-1111-111111111111"}}}},"400":{"description":"The body or a value in it is not valid","content":{"application/json":{"schema":{"type":"object","properties":{"error_reference":{"type":"string","description":"Quote this when reporting a failure."},"error_type":{"type":"string"},"errors":{"type":"object","properties":{},"description":"Field name, or `message`, to a list of messages.","additionalProperties":true},"status_code":{"type":"integer"},"timestamp":{"type":"string"}},"required":["error_reference","error_type","errors","status_code","timestamp"],"description":"Every error from this service, whatever the cause. Rendered by InfrastructureError.","additionalProperties":true},"example":{"error_reference":"b7825cd3-59dc-43a9-9e1f-964676d3cf72","error_type":"NOT_FOUND_ERROR","errors":{"message":["Product with reference 11111111-1111-1111-1111-111111111111 not found."]},"status_code":400,"timestamp":"2026-09-30T10:00:00.000000"}}}},"401":{"description":"No token, an expired token, or not allowed to do this","content":{"application/json":{"schema":{"type":"object","properties":{"error_reference":{"type":"string","description":"Quote this when reporting a failure."},"error_type":{"type":"string"},"errors":{"type":"object","properties":{},"description":"Field name, or `message`, to a list of messages.","additionalProperties":true},"status_code":{"type":"integer"},"timestamp":{"type":"string"}},"required":["error_reference","error_type","errors","status_code","timestamp"],"description":"Every error from this service, whatever the cause. Rendered by InfrastructureError.","additionalProperties":true},"example":{"error_reference":"b7825cd3-59dc-43a9-9e1f-964676d3cf72","error_type":"NOT_FOUND_ERROR","errors":{"message":["Product with reference 11111111-1111-1111-1111-111111111111 not found."]},"status_code":401,"timestamp":"2026-09-30T10:00:00.000000"}}}},"404":{"description":"No such record","content":{"application/json":{"schema":{"type":"object","properties":{"error_reference":{"type":"string","description":"Quote this when reporting a failure."},"error_type":{"type":"string"},"errors":{"type":"object","properties":{},"description":"Field name, or `message`, to a list of messages.","additionalProperties":true},"status_code":{"type":"integer"},"timestamp":{"type":"string"}},"required":["error_reference","error_type","errors","status_code","timestamp"],"description":"Every error from this service, whatever the cause. Rendered by InfrastructureError.","additionalProperties":true},"example":{"error_reference":"b7825cd3-59dc-43a9-9e1f-964676d3cf72","error_type":"NOT_FOUND_ERROR","errors":{"message":["Product with reference 11111111-1111-1111-1111-111111111111 not found."]},"status_code":404,"timestamp":"2026-09-30T10:00:00.000000"}}}},"409":{"description":"A record with that name already exists","content":{"application/json":{"schema":{"type":"object","properties":{"error_reference":{"type":"string","description":"Quote this when reporting a failure."},"error_type":{"type":"string"},"errors":{"type":"object","properties":{},"description":"Field name, or `message`, to a list of messages.","additionalProperties":true},"status_code":{"type":"integer"},"timestamp":{"type":"string"}},"required":["error_reference","error_type","errors","status_code","timestamp"],"description":"Every error from this service, whatever the cause. Rendered by InfrastructureError.","additionalProperties":true},"example":{"error_reference":"b7825cd3-59dc-43a9-9e1f-964676d3cf72","error_type":"NOT_FOUND_ERROR","errors":{"message":["Product with reference 11111111-1111-1111-1111-111111111111 not found."]},"status_code":409,"timestamp":"2026-09-30T10:00:00.000000"}}}},"500":{"description":"Unexpected failure. Quote `error_reference` when reporting it","content":{"application/json":{"schema":{"type":"object","properties":{"error_reference":{"type":"string","description":"Quote this when reporting a failure."},"error_type":{"type":"string"},"errors":{"type":"object","properties":{},"description":"Field name, or `message`, to a list of messages.","additionalProperties":true},"status_code":{"type":"integer"},"timestamp":{"type":"string"}},"required":["error_reference","error_type","errors","status_code","timestamp"],"description":"Every error from this service, whatever the cause. Rendered by InfrastructureError.","additionalProperties":true},"example":{"error_reference":"b7825cd3-59dc-43a9-9e1f-964676d3cf72","error_type":"NOT_FOUND_ERROR","errors":{"message":["Product with reference 11111111-1111-1111-1111-111111111111 not found."]},"status_code":500,"timestamp":"2026-09-30T10:00:00.000000"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}},"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"max_users":{"type":["integer","null"]},"multiplier":{"type":["number","null"]},"duration":{"type":["integer","null"]},"metadata":{"type":["object","null"],"additionalProperties":true}},"description":"Send only what changes."},"example":{"max_users":10}}}}}},"/products/licenses":{"post":{"tags":["Licenses"],"summary":"Create a licence type","description":"A type and duration pair must be unique.","operationId":"create_license_products_licenses_post","security":[{"HTTPBearer":[]}],"responses":{"201":{"description":"Created","content":{"application/json":{"schema":{"type":"object","properties":{"reference":{"type":"string"}},"required":["reference"],"description":"The reference of the record that was created, changed or removed.","additionalProperties":true},"example":{"reference":"11111111-1111-1111-1111-111111111111"}}}},"400":{"description":"The body or a value in it is not valid","content":{"application/json":{"schema":{"type":"object","properties":{"error_reference":{"type":"string","description":"Quote this when reporting a failure."},"error_type":{"type":"string"},"errors":{"type":"object","properties":{},"description":"Field name, or `message`, to a list of messages.","additionalProperties":true},"status_code":{"type":"integer"},"timestamp":{"type":"string"}},"required":["error_reference","error_type","errors","status_code","timestamp"],"description":"Every error from this service, whatever the cause. Rendered by InfrastructureError.","additionalProperties":true},"example":{"error_reference":"b7825cd3-59dc-43a9-9e1f-964676d3cf72","error_type":"NOT_FOUND_ERROR","errors":{"message":["Product with reference 11111111-1111-1111-1111-111111111111 not found."]},"status_code":400,"timestamp":"2026-09-30T10:00:00.000000"}}}},"401":{"description":"No token, an expired token, or not allowed to do this","content":{"application/json":{"schema":{"type":"object","properties":{"error_reference":{"type":"string","description":"Quote this when reporting a failure."},"error_type":{"type":"string"},"errors":{"type":"object","properties":{},"description":"Field name, or `message`, to a list of messages.","additionalProperties":true},"status_code":{"type":"integer"},"timestamp":{"type":"string"}},"required":["error_reference","error_type","errors","status_code","timestamp"],"description":"Every error from this service, whatever the cause. Rendered by InfrastructureError.","additionalProperties":true},"example":{"error_reference":"b7825cd3-59dc-43a9-9e1f-964676d3cf72","error_type":"NOT_FOUND_ERROR","errors":{"message":["Product with reference 11111111-1111-1111-1111-111111111111 not found."]},"status_code":401,"timestamp":"2026-09-30T10:00:00.000000"}}}},"409":{"description":"A record with that name already exists","content":{"application/json":{"schema":{"type":"object","properties":{"error_reference":{"type":"string","description":"Quote this when reporting a failure."},"error_type":{"type":"string"},"errors":{"type":"object","properties":{},"description":"Field name, or `message`, to a list of messages.","additionalProperties":true},"status_code":{"type":"integer"},"timestamp":{"type":"string"}},"required":["error_reference","error_type","errors","status_code","timestamp"],"description":"Every error from this service, whatever the cause. Rendered by InfrastructureError.","additionalProperties":true},"example":{"error_reference":"b7825cd3-59dc-43a9-9e1f-964676d3cf72","error_type":"NOT_FOUND_ERROR","errors":{"message":["Product with reference 11111111-1111-1111-1111-111111111111 not found."]},"status_code":409,"timestamp":"2026-09-30T10:00:00.000000"}}}},"500":{"description":"Unexpected failure. Quote `error_reference` when reporting it","content":{"application/json":{"schema":{"type":"object","properties":{"error_reference":{"type":"string","description":"Quote this when reporting a failure."},"error_type":{"type":"string"},"errors":{"type":"object","properties":{},"description":"Field name, or `message`, to a list of messages.","additionalProperties":true},"status_code":{"type":"integer"},"timestamp":{"type":"string"}},"required":["error_reference","error_type","errors","status_code","timestamp"],"description":"Every error from this service, whatever the cause. Rendered by InfrastructureError.","additionalProperties":true},"example":{"error_reference":"b7825cd3-59dc-43a9-9e1f-964676d3cf72","error_type":"NOT_FOUND_ERROR","errors":{"message":["Product with reference 11111111-1111-1111-1111-111111111111 not found."]},"status_code":500,"timestamp":"2026-09-30T10:00:00.000000"}}}}},"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"license_type":{"type":"string","description":"For example `Essential`, `Premium` or `Enterprise`."},"max_users":{"type":"integer"},"multiplier":{"type":"number"},"duration":{"type":"integer","description":"Months."},"metadata":{"type":"object","additionalProperties":true},"reference":{"type":"string","description":"Optional. Generated by the service when left out."}},"required":["license_type","max_users","multiplier","duration"]},"example":{"license_type":"Premium","max_users":5,"multiplier":1.0,"duration":12}}}}},"get":{"tags":["Licenses"],"summary":"List licence types","description":"Paged, ten to a page. Any field of the record can be used as a filter\n(`?field=value`), not only the ones listed; text is matched partially and case-insensitively.\n`all` searches across the text fields instead. `ordering=field` sorts ascending,\n`ordering=-field` descending; the default is newest first.\n\nThe list is under `licenses`, not `items`.","operationId":"get_licenses_products_licenses_get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"page","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Page number. `all` turns paging off.","examples":["1"],"title":"Page"},"description":"Page number. `all` turns paging off."},{"name":"ordering","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"`field` ascending, `-field` descending. Default `-created_at_timestamp`.","examples":["license_type"],"title":"Ordering"},"description":"`field` ascending, `-field` descending. Default `-created_at_timestamp`."},{"name":"all","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Search across the text fields.","title":"All"},"description":"Search across the text fields."},{"name":"license_type","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Partial, case-insensitive, for example `Premium`.","title":"License Type"},"description":"Partial, case-insensitive, for example `Premium`."}],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"type":"object","properties":{"licenses":{"type":"array","items":{"type":"object","properties":{"reference":{"type":"string"},"license_type":{"type":"string"},"max_users":{"type":"integer"},"multiplier":{"type":"number"},"duration":{"type":"integer","description":"Months."},"metadata":{"type":"object","additionalProperties":true}},"required":["reference","license_type","max_users","multiplier","duration","metadata"],"description":"A licence type, as `License.to_dict` renders it.","additionalProperties":true}},"pagination":{"type":"object","properties":{"current_page":{"type":"integer"},"page_size":{"type":"integer"},"total_records":{"type":"integer"},"total_pages":{"type":"integer"},"has_next":{"type":"boolean"},"has_previous":{"type":"boolean"}},"required":["current_page","page_size","total_records","total_pages","has_next","has_previous"],"additionalProperties":true}},"required":["licenses","pagination"],"description":"The list is under `licenses`, not `items`.","additionalProperties":true},"example":{"licenses":[{"reference":"55555555-5555-5555-5555-555555555555","license_type":"Premium","max_users":5,"multiplier":1.0,"duration":12,"metadata":{}}],"pagination":{"current_page":1,"page_size":10,"total_records":1,"total_pages":1,"has_next":false,"has_previous":false}}}}},"401":{"description":"No token, an expired token, or not allowed to do this","content":{"application/json":{"schema":{"type":"object","properties":{"error_reference":{"type":"string","description":"Quote this when reporting a failure."},"error_type":{"type":"string"},"errors":{"type":"object","properties":{},"description":"Field name, or `message`, to a list of messages.","additionalProperties":true},"status_code":{"type":"integer"},"timestamp":{"type":"string"}},"required":["error_reference","error_type","errors","status_code","timestamp"],"description":"Every error from this service, whatever the cause. Rendered by InfrastructureError.","additionalProperties":true},"example":{"error_reference":"b7825cd3-59dc-43a9-9e1f-964676d3cf72","error_type":"NOT_FOUND_ERROR","errors":{"message":["Product with reference 11111111-1111-1111-1111-111111111111 not found."]},"status_code":401,"timestamp":"2026-09-30T10:00:00.000000"}}}},"500":{"description":"Unexpected failure. Quote `error_reference` when reporting it","content":{"application/json":{"schema":{"type":"object","properties":{"error_reference":{"type":"string","description":"Quote this when reporting a failure."},"error_type":{"type":"string"},"errors":{"type":"object","properties":{},"description":"Field name, or `message`, to a list of messages.","additionalProperties":true},"status_code":{"type":"integer"},"timestamp":{"type":"string"}},"required":["error_reference","error_type","errors","status_code","timestamp"],"description":"Every error from this service, whatever the cause. Rendered by InfrastructureError.","additionalProperties":true},"example":{"error_reference":"b7825cd3-59dc-43a9-9e1f-964676d3cf72","error_type":"NOT_FOUND_ERROR","errors":{"message":["Product with reference 11111111-1111-1111-1111-111111111111 not found."]},"status_code":500,"timestamp":"2026-09-30T10:00:00.000000"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/products/health":{"get":{"tags":["Products"],"summary":"Health check","operationId":"health_products_health_get","responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"properties":{"message":{"type":"string"}},"additionalProperties":true,"type":"object","required":["message"]},"example":{"message":"marve-py-product-service is okay and running, fast!"}}}},"500":{"description":"Unexpected failure. Quote `error_reference` when reporting it","content":{"application/json":{"schema":{"properties":{"error_reference":{"type":"string","description":"Quote this when reporting a failure."},"error_type":{"type":"string"},"errors":{"properties":{},"additionalProperties":true,"type":"object","description":"Field name, or `message`, to a list of messages."},"status_code":{"type":"integer"},"timestamp":{"type":"string"}},"additionalProperties":true,"type":"object","required":["error_reference","error_type","errors","status_code","timestamp"],"description":"Every error from this service, whatever the cause. Rendered by InfrastructureError."},"example":{"error_reference":"b7825cd3-59dc-43a9-9e1f-964676d3cf72","error_type":"NOT_FOUND_ERROR","errors":{"message":["Product with reference 11111111-1111-1111-1111-111111111111 not found."]},"status_code":500,"timestamp":"2026-09-30T10:00:00.000000"}}}}}}},"/products":{"post":{"tags":["Products"],"summary":"Create a product","description":"The name must be unique. Any `features` given are created as features too.","operationId":"create_product_products_post","security":[{"HTTPBearer":[]}],"responses":{"201":{"description":"Created","content":{"application/json":{"schema":{"type":"object","properties":{"reference":{"type":"string"}},"required":["reference"],"description":"The reference of the record that was created, changed or removed.","additionalProperties":true},"example":{"reference":"11111111-1111-1111-1111-111111111111"}}}},"400":{"description":"The body or a value in it is not valid","content":{"application/json":{"schema":{"type":"object","properties":{"error_reference":{"type":"string","description":"Quote this when reporting a failure."},"error_type":{"type":"string"},"errors":{"type":"object","properties":{},"description":"Field name, or `message`, to a list of messages.","additionalProperties":true},"status_code":{"type":"integer"},"timestamp":{"type":"string"}},"required":["error_reference","error_type","errors","status_code","timestamp"],"description":"Every error from this service, whatever the cause. Rendered by InfrastructureError.","additionalProperties":true},"example":{"error_reference":"b7825cd3-59dc-43a9-9e1f-964676d3cf72","error_type":"NOT_FOUND_ERROR","errors":{"message":["Product with reference 11111111-1111-1111-1111-111111111111 not found."]},"status_code":400,"timestamp":"2026-09-30T10:00:00.000000"}}}},"401":{"description":"No token, an expired token, or not allowed to do this","content":{"application/json":{"schema":{"type":"object","properties":{"error_reference":{"type":"string","description":"Quote this when reporting a failure."},"error_type":{"type":"string"},"errors":{"type":"object","properties":{},"description":"Field name, or `message`, to a list of messages.","additionalProperties":true},"status_code":{"type":"integer"},"timestamp":{"type":"string"}},"required":["error_reference","error_type","errors","status_code","timestamp"],"description":"Every error from this service, whatever the cause. Rendered by InfrastructureError.","additionalProperties":true},"example":{"error_reference":"b7825cd3-59dc-43a9-9e1f-964676d3cf72","error_type":"NOT_FOUND_ERROR","errors":{"message":["Product with reference 11111111-1111-1111-1111-111111111111 not found."]},"status_code":401,"timestamp":"2026-09-30T10:00:00.000000"}}}},"409":{"description":"A record with that name already exists","content":{"application/json":{"schema":{"type":"object","properties":{"error_reference":{"type":"string","description":"Quote this when reporting a failure."},"error_type":{"type":"string"},"errors":{"type":"object","properties":{},"description":"Field name, or `message`, to a list of messages.","additionalProperties":true},"status_code":{"type":"integer"},"timestamp":{"type":"string"}},"required":["error_reference","error_type","errors","status_code","timestamp"],"description":"Every error from this service, whatever the cause. Rendered by InfrastructureError.","additionalProperties":true},"example":{"error_reference":"b7825cd3-59dc-43a9-9e1f-964676d3cf72","error_type":"NOT_FOUND_ERROR","errors":{"message":["Product with reference 11111111-1111-1111-1111-111111111111 not found."]},"status_code":409,"timestamp":"2026-09-30T10:00:00.000000"}}}},"500":{"description":"Unexpected failure. Quote `error_reference` when reporting it","content":{"application/json":{"schema":{"type":"object","properties":{"error_reference":{"type":"string","description":"Quote this when reporting a failure."},"error_type":{"type":"string"},"errors":{"type":"object","properties":{},"description":"Field name, or `message`, to a list of messages.","additionalProperties":true},"status_code":{"type":"integer"},"timestamp":{"type":"string"}},"required":["error_reference","error_type","errors","status_code","timestamp"],"description":"Every error from this service, whatever the cause. Rendered by InfrastructureError.","additionalProperties":true},"example":{"error_reference":"b7825cd3-59dc-43a9-9e1f-964676d3cf72","error_type":"NOT_FOUND_ERROR","errors":{"message":["Product with reference 11111111-1111-1111-1111-111111111111 not found."]},"status_code":500,"timestamp":"2026-09-30T10:00:00.000000"}}}}},"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"product_name":{"type":"string"},"description":{"type":"string"},"features":{"type":"array","description":"Features to create with the product, each a feature create body.","items":{"type":"object","additionalProperties":true}},"metadata":{"type":"object","additionalProperties":true},"reference":{"type":"string","description":"Optional. Generated by the service when left out."},"created_at_timestamp":{"type":"string","description":"Optional. Generated by the service when left out."},"updated_at_timestamp":{"type":"string","description":"Optional. Generated by the service when left out."}},"required":["product_name","description"]},"example":{"product_name":"Ports","description":"Port prediction product"}}}}},"get":{"tags":["Products"],"summary":"List products","description":"Paged, ten to a page. Any field of the record can be used as a filter\n(`?field=value`), not only the ones listed; text is matched partially and case-insensitively.\n`all` searches across the text fields instead. `ordering=field` sorts ascending,\n`ordering=-field` descending; the default is newest first.","operationId":"get_products_products_get","parameters":[{"name":"page","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Page number. `all` turns paging off.","examples":["1"],"title":"Page"},"description":"Page number. `all` turns paging off."},{"name":"ordering","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"`field` ascending, `-field` descending. Default `-created_at_timestamp`.","examples":["product_name"],"title":"Ordering"},"description":"`field` ascending, `-field` descending. Default `-created_at_timestamp`."},{"name":"all","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Search across the text fields.","title":"All"},"description":"Search across the text fields."},{"name":"product_name","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Partial, case-insensitive.","title":"Product Name"},"description":"Partial, case-insensitive."}],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"type":"object","properties":{"items":{"type":"array","items":{"type":"object","properties":{"reference":{"type":"string"},"product_name":{"type":"string"},"description":{"type":["string","null"]},"features":{"type":"array","description":"The product's features, each shaped like a feature record.","items":{"type":"object","additionalProperties":true}},"metadata":{"type":"object","additionalProperties":true},"created_by_reference":{"type":["string","null"]},"updated_by_reference":{"type":["string","null"]},"created_at_timestamp":{"type":["string","null"]},"updated_at_timestamp":{"type":["string","null"]}},"required":["reference","product_name","description","features","metadata","created_by_reference","updated_by_reference","created_at_timestamp","updated_at_timestamp"],"description":"A product, as `Product.to_dict` renders it.","additionalProperties":true}},"pagination":{"type":"object","properties":{"current_page":{"type":"integer"},"page_size":{"type":"integer"},"total_records":{"type":"integer"},"total_pages":{"type":"integer"},"has_next":{"type":"boolean"},"has_previous":{"type":"boolean"}},"required":["current_page","page_size","total_records","total_pages","has_next","has_previous"],"additionalProperties":true}},"required":["items","pagination"],"additionalProperties":true},"example":{"items":[{"reference":"11111111-1111-1111-1111-111111111111","product_name":"Ports","description":"Port prediction product","features":[{"reference":"22222222-2222-2222-2222-222222222222","feature_name":"Tidal streams","description":"A catalogue feature","feature_type":"model","base_price":100.0,"prediction":1,"hindcast":1,"box_size":"1x1","price_multiplier":1.0,"min_boxes":1,"boxes":[{"lat_min":50.0,"lat_max":51.0,"lon_min":-2.0,"lon_max":-1.0,"coverage":100.0,"bath_value":10.0}],"total_price":100.0,"metadata":{},"created_by_reference":"33333333-3333-3333-3333-333333333333","updated_by_reference":"33333333-3333-3333-3333-333333333333","created_at_timestamp":"2026-01-01T09:30:00","updated_at_timestamp":"2026-01-01T09:30:00"}],"metadata":{},"created_by_reference":"33333333-3333-3333-3333-333333333333","updated_by_reference":"33333333-3333-3333-3333-333333333333","created_at_timestamp":"2026-01-01T09:30:00","updated_at_timestamp":"2026-01-01T09:30:00"}],"pagination":{"current_page":1,"page_size":10,"total_records":1,"total_pages":1,"has_next":false,"has_previous":false}}}}},"500":{"description":"Unexpected failure. Quote `error_reference` when reporting it","content":{"application/json":{"schema":{"type":"object","properties":{"error_reference":{"type":"string","description":"Quote this when reporting a failure."},"error_type":{"type":"string"},"errors":{"type":"object","properties":{},"description":"Field name, or `message`, to a list of messages.","additionalProperties":true},"status_code":{"type":"integer"},"timestamp":{"type":"string"}},"required":["error_reference","error_type","errors","status_code","timestamp"],"description":"Every error from this service, whatever the cause. Rendered by InfrastructureError.","additionalProperties":true},"example":{"error_reference":"b7825cd3-59dc-43a9-9e1f-964676d3cf72","error_type":"NOT_FOUND_ERROR","errors":{"message":["Product with reference 11111111-1111-1111-1111-111111111111 not found."]},"status_code":500,"timestamp":"2026-09-30T10:00:00.000000"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/products/{reference}/features/{feature_reference}":{"get":{"tags":["Products"],"summary":"One of a product's features","operationId":"get_product_feature_products__reference__features__feature_reference__get","parameters":[{"name":"reference","in":"path","required":true,"schema":{"type":"string","title":"Reference"}},{"name":"feature_reference","in":"path","required":true,"schema":{"type":"string","title":"Feature Reference"}}],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"type":"object","properties":{"reference":{"type":"string"},"feature_name":{"type":"string"},"description":{"type":["string","null"]},"feature_type":{"type":"string","description":"For example `model`, `port` or `data`."},"base_price":{"type":"number"},"prediction":{"type":["integer","null"],"description":"Model features only; null otherwise."},"hindcast":{"type":["integer","null"],"description":"Model features only; null otherwise."},"box_size":{"type":["string","null"],"description":"`NxN` in degrees. Model features only."},"price_multiplier":{"type":["number","null"],"description":"Price per extra box. Model features only."},"min_boxes":{"type":"integer"},"boxes":{"type":"array","description":"Geographic boxes. Model features only.","items":{"type":"object","properties":{"lat_min":{"type":"number"},"lat_max":{"type":"number"},"lon_min":{"type":"number"},"lon_max":{"type":"number"},"coverage":{"type":"number"},"bath_value":{"type":"number"}},"additionalProperties":true}},"total_price":{"type":"number"},"metadata":{"type":"object","additionalProperties":true},"created_by_reference":{"type":["string","null"]},"updated_by_reference":{"type":["string","null"]},"created_at_timestamp":{"type":["string","null"]},"updated_at_timestamp":{"type":["string","null"]}},"required":["reference","feature_name","description","feature_type","base_price","prediction","hindcast","box_size","price_multiplier","min_boxes","boxes","total_price","metadata","created_by_reference","updated_by_reference","created_at_timestamp","updated_at_timestamp"],"description":"A catalogue feature, as `Feature.to_dict` renders it.","additionalProperties":true},"example":{"reference":"22222222-2222-2222-2222-222222222222","feature_name":"Tidal streams","description":"A catalogue feature","feature_type":"model","base_price":100.0,"prediction":1,"hindcast":1,"box_size":"1x1","price_multiplier":1.0,"min_boxes":1,"boxes":[{"lat_min":50.0,"lat_max":51.0,"lon_min":-2.0,"lon_max":-1.0,"coverage":100.0,"bath_value":10.0}],"total_price":100.0,"metadata":{},"created_by_reference":"33333333-3333-3333-3333-333333333333","updated_by_reference":"33333333-3333-3333-3333-333333333333","created_at_timestamp":"2026-01-01T09:30:00","updated_at_timestamp":"2026-01-01T09:30:00"}}}},"404":{"description":"No such record","content":{"application/json":{"schema":{"type":"object","properties":{"error_reference":{"type":"string","description":"Quote this when reporting a failure."},"error_type":{"type":"string"},"errors":{"type":"object","properties":{},"description":"Field name, or `message`, to a list of messages.","additionalProperties":true},"status_code":{"type":"integer"},"timestamp":{"type":"string"}},"required":["error_reference","error_type","errors","status_code","timestamp"],"description":"Every error from this service, whatever the cause. Rendered by InfrastructureError.","additionalProperties":true},"example":{"error_reference":"b7825cd3-59dc-43a9-9e1f-964676d3cf72","error_type":"NOT_FOUND_ERROR","errors":{"message":["Product with reference 11111111-1111-1111-1111-111111111111 not found."]},"status_code":404,"timestamp":"2026-09-30T10:00:00.000000"}}}},"500":{"description":"Unexpected failure. Quote `error_reference` when reporting it","content":{"application/json":{"schema":{"type":"object","properties":{"error_reference":{"type":"string","description":"Quote this when reporting a failure."},"error_type":{"type":"string"},"errors":{"type":"object","properties":{},"description":"Field name, or `message`, to a list of messages.","additionalProperties":true},"status_code":{"type":"integer"},"timestamp":{"type":"string"}},"required":["error_reference","error_type","errors","status_code","timestamp"],"description":"Every error from this service, whatever the cause. Rendered by InfrastructureError.","additionalProperties":true},"example":{"error_reference":"b7825cd3-59dc-43a9-9e1f-964676d3cf72","error_type":"NOT_FOUND_ERROR","errors":{"message":["Product with reference 11111111-1111-1111-1111-111111111111 not found."]},"status_code":500,"timestamp":"2026-09-30T10:00:00.000000"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"delete":{"tags":["Products"],"summary":"Remove a feature from a product","operationId":"remove_product_feature_products__reference__features__feature_reference__delete","security":[{"HTTPBearer":[]}],"parameters":[{"name":"reference","in":"path","required":true,"schema":{"type":"string","title":"Reference"}},{"name":"feature_reference","in":"path","required":true,"schema":{"type":"string","title":"Feature Reference"}}],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"type":"object","properties":{"reference":{"type":"string"},"message":{"type":"string"}},"required":["reference","message"],"additionalProperties":true},"example":{"reference":"44444444-4444-4444-4444-444444444444","message":"Feature 22222222-2222-2222-2222-222222222222 removed from bundle 44444444-4444-4444-4444-444444444444"}}}},"401":{"description":"No token, an expired token, or not allowed to do this","content":{"application/json":{"schema":{"type":"object","properties":{"error_reference":{"type":"string","description":"Quote this when reporting a failure."},"error_type":{"type":"string"},"errors":{"type":"object","properties":{},"description":"Field name, or `message`, to a list of messages.","additionalProperties":true},"status_code":{"type":"integer"},"timestamp":{"type":"string"}},"required":["error_reference","error_type","errors","status_code","timestamp"],"description":"Every error from this service, whatever the cause. Rendered by InfrastructureError.","additionalProperties":true},"example":{"error_reference":"b7825cd3-59dc-43a9-9e1f-964676d3cf72","error_type":"NOT_FOUND_ERROR","errors":{"message":["Product with reference 11111111-1111-1111-1111-111111111111 not found."]},"status_code":401,"timestamp":"2026-09-30T10:00:00.000000"}}}},"404":{"description":"No such record","content":{"application/json":{"schema":{"type":"object","properties":{"error_reference":{"type":"string","description":"Quote this when reporting a failure."},"error_type":{"type":"string"},"errors":{"type":"object","properties":{},"description":"Field name, or `message`, to a list of messages.","additionalProperties":true},"status_code":{"type":"integer"},"timestamp":{"type":"string"}},"required":["error_reference","error_type","errors","status_code","timestamp"],"description":"Every error from this service, whatever the cause. Rendered by InfrastructureError.","additionalProperties":true},"example":{"error_reference":"b7825cd3-59dc-43a9-9e1f-964676d3cf72","error_type":"NOT_FOUND_ERROR","errors":{"message":["Product with reference 11111111-1111-1111-1111-111111111111 not found."]},"status_code":404,"timestamp":"2026-09-30T10:00:00.000000"}}}},"500":{"description":"Unexpected failure. Quote `error_reference` when reporting it","content":{"application/json":{"schema":{"type":"object","properties":{"error_reference":{"type":"string","description":"Quote this when reporting a failure."},"error_type":{"type":"string"},"errors":{"type":"object","properties":{},"description":"Field name, or `message`, to a list of messages.","additionalProperties":true},"status_code":{"type":"integer"},"timestamp":{"type":"string"}},"required":["error_reference","error_type","errors","status_code","timestamp"],"description":"Every error from this service, whatever the cause. Rendered by InfrastructureError.","additionalProperties":true},"example":{"error_reference":"b7825cd3-59dc-43a9-9e1f-964676d3cf72","error_type":"NOT_FOUND_ERROR","errors":{"message":["Product with reference 11111111-1111-1111-1111-111111111111 not found."]},"status_code":500,"timestamp":"2026-09-30T10:00:00.000000"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/products/{reference}/bundles/{bundle_reference}":{"put":{"tags":["Bundles"],"summary":"Update a bundle","description":"Partial: send only what changes. `feature_references`, when given, is the complete new list.","operationId":"update_product_bundle_products__reference__bundles__bundle_reference__put","security":[{"HTTPBearer":[]}],"parameters":[{"name":"reference","in":"path","required":true,"schema":{"type":"string","title":"Reference"}},{"name":"bundle_reference","in":"path","required":true,"schema":{"type":"string","title":"Bundle Reference"}}],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"type":"object","properties":{"reference":{"type":"string"}},"required":["reference"],"description":"The reference of the record that was created, changed or removed.","additionalProperties":true},"example":{"reference":"11111111-1111-1111-1111-111111111111"}}}},"400":{"description":"The body or a value in it is not valid","content":{"application/json":{"schema":{"type":"object","properties":{"error_reference":{"type":"string","description":"Quote this when reporting a failure."},"error_type":{"type":"string"},"errors":{"type":"object","properties":{},"description":"Field name, or `message`, to a list of messages.","additionalProperties":true},"status_code":{"type":"integer"},"timestamp":{"type":"string"}},"required":["error_reference","error_type","errors","status_code","timestamp"],"description":"Every error from this service, whatever the cause. Rendered by InfrastructureError.","additionalProperties":true},"example":{"error_reference":"b7825cd3-59dc-43a9-9e1f-964676d3cf72","error_type":"NOT_FOUND_ERROR","errors":{"message":["Product with reference 11111111-1111-1111-1111-111111111111 not found."]},"status_code":400,"timestamp":"2026-09-30T10:00:00.000000"}}}},"401":{"description":"No token, an expired token, or not allowed to do this","content":{"application/json":{"schema":{"type":"object","properties":{"error_reference":{"type":"string","description":"Quote this when reporting a failure."},"error_type":{"type":"string"},"errors":{"type":"object","properties":{},"description":"Field name, or `message`, to a list of messages.","additionalProperties":true},"status_code":{"type":"integer"},"timestamp":{"type":"string"}},"required":["error_reference","error_type","errors","status_code","timestamp"],"description":"Every error from this service, whatever the cause. Rendered by InfrastructureError.","additionalProperties":true},"example":{"error_reference":"b7825cd3-59dc-43a9-9e1f-964676d3cf72","error_type":"NOT_FOUND_ERROR","errors":{"message":["Product with reference 11111111-1111-1111-1111-111111111111 not found."]},"status_code":401,"timestamp":"2026-09-30T10:00:00.000000"}}}},"404":{"description":"No such record","content":{"application/json":{"schema":{"type":"object","properties":{"error_reference":{"type":"string","description":"Quote this when reporting a failure."},"error_type":{"type":"string"},"errors":{"type":"object","properties":{},"description":"Field name, or `message`, to a list of messages.","additionalProperties":true},"status_code":{"type":"integer"},"timestamp":{"type":"string"}},"required":["error_reference","error_type","errors","status_code","timestamp"],"description":"Every error from this service, whatever the cause. Rendered by InfrastructureError.","additionalProperties":true},"example":{"error_reference":"b7825cd3-59dc-43a9-9e1f-964676d3cf72","error_type":"NOT_FOUND_ERROR","errors":{"message":["Product with reference 11111111-1111-1111-1111-111111111111 not found."]},"status_code":404,"timestamp":"2026-09-30T10:00:00.000000"}}}},"409":{"description":"A record with that name already exists","content":{"application/json":{"schema":{"type":"object","properties":{"error_reference":{"type":"string","description":"Quote this when reporting a failure."},"error_type":{"type":"string"},"errors":{"type":"object","properties":{},"description":"Field name, or `message`, to a list of messages.","additionalProperties":true},"status_code":{"type":"integer"},"timestamp":{"type":"string"}},"required":["error_reference","error_type","errors","status_code","timestamp"],"description":"Every error from this service, whatever the cause. Rendered by InfrastructureError.","additionalProperties":true},"example":{"error_reference":"b7825cd3-59dc-43a9-9e1f-964676d3cf72","error_type":"NOT_FOUND_ERROR","errors":{"message":["Product with reference 11111111-1111-1111-1111-111111111111 not found."]},"status_code":409,"timestamp":"2026-09-30T10:00:00.000000"}}}},"500":{"description":"Unexpected failure. Quote `error_reference` when reporting it","content":{"application/json":{"schema":{"type":"object","properties":{"error_reference":{"type":"string","description":"Quote this when reporting a failure."},"error_type":{"type":"string"},"errors":{"type":"object","properties":{},"description":"Field name, or `message`, to a list of messages.","additionalProperties":true},"status_code":{"type":"integer"},"timestamp":{"type":"string"}},"required":["error_reference","error_type","errors","status_code","timestamp"],"description":"Every error from this service, whatever the cause. Rendered by InfrastructureError.","additionalProperties":true},"example":{"error_reference":"b7825cd3-59dc-43a9-9e1f-964676d3cf72","error_type":"NOT_FOUND_ERROR","errors":{"message":["Product with reference 11111111-1111-1111-1111-111111111111 not found."]},"status_code":500,"timestamp":"2026-09-30T10:00:00.000000"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}},"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"bundle_name":{"type":["string","null"]},"bundle_type":{"type":["string","null"]},"description":{"type":["string","null"]},"license_reference":{"type":["string","null"]},"feature_references":{"type":["array","null"],"description":"When given, the complete new list.","items":{"type":"string"}},"selected_boxes":{"type":["array","null"],"items":{"type":"object","properties":{"lat_min":{"type":"number"},"lat_max":{"type":"number"},"lon_min":{"type":"number"},"lon_max":{"type":"number"},"coverage":{"type":"number"},"bath_value":{"type":"number"}},"additionalProperties":true}},"discount_reference":{"type":["string","null"]},"base_price":{"type":["number","null"]},"metadata":{"type":["object","null"],"additionalProperties":true},"updated_at_timestamp":{"type":"string","description":"Optional. Generated by the service when left out."}},"description":"Send only what changes."},"example":{"description":"Updated description"}}}}},"get":{"tags":["Bundles"],"summary":"One of a product's bundles","operationId":"get_product_bundle_by_reference_products__reference__bundles__bundle_reference__get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"reference","in":"path","required":true,"schema":{"type":"string","title":"Reference"}},{"name":"bundle_reference","in":"path","required":true,"schema":{"type":"string","title":"Bundle Reference"}}],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"type":"object","properties":{"reference":{"type":"string"},"product_reference":{"type":"string"},"product":{"type":"object","description":"The product's name and description, copied onto the bundle.","additionalProperties":true},"bundle_name":{"type":"string"},"bundle_type":{"type":"string","description":"For example `user` or `organisation`."},"description":{"type":["string","null"]},"base_price":{"type":"number"},"license":{"type":"object","description":"The licence record the bundle carries, or `{}`.","additionalProperties":true},"features":{"type":"array","description":"Feature records, each with the `selected_boxes` that belong to it.","items":{"type":"object","additionalProperties":true}},"selected_boxes":{"type":"array","items":{"type":"object","properties":{"lat_min":{"type":"number"},"lat_max":{"type":"number"},"lon_min":{"type":"number"},"lon_max":{"type":"number"},"coverage":{"type":"number"},"bath_value":{"type":"number"}},"additionalProperties":true}},"discount":{"type":"object","description":"The discount record the bundle carries, or `{}`.","additionalProperties":true},"total_price":{"type":"number"},"metadata":{"type":"object","additionalProperties":true},"created_by_reference":{"type":["string","null"]},"updated_by_reference":{"type":["string","null"]},"created_at_timestamp":{"type":["string","null"]},"updated_at_timestamp":{"type":["string","null"]},"original_price_before_discount":{"type":"number"},"applied_discount_value":{"type":"number"},"final_price_after_discount":{"type":"number"}},"required":["reference","product_reference","product","bundle_name","bundle_type","description","base_price","license","features","selected_boxes","discount","total_price","metadata","created_by_reference","updated_by_reference","created_at_timestamp","updated_at_timestamp","original_price_before_discount","applied_discount_value","final_price_after_discount"],"description":"A bundle, as `Bundle.to_dict` renders it, with its price worked out.","additionalProperties":true},"example":{"reference":"44444444-4444-4444-4444-444444444444","product_reference":"11111111-1111-1111-1111-111111111111","product":{"product_name":"Ports","description":"Port prediction product"},"bundle_name":"Premium ports","bundle_type":"user","description":"A premium ports bundle","base_price":100.0,"license":{"reference":"55555555-5555-5555-5555-555555555555","license_type":"Premium","max_users":5,"multiplier":1.0,"duration":12,"metadata":{}},"features":[{"reference":"22222222-2222-2222-2222-222222222222","feature_name":"Tidal streams","description":"A catalogue feature","feature_type":"model","base_price":100.0,"prediction":1,"hindcast":1,"box_size":"1x1","price_multiplier":1.0,"min_boxes":1,"boxes":[{"lat_min":50.0,"lat_max":51.0,"lon_min":-2.0,"lon_max":-1.0,"coverage":100.0,"bath_value":10.0}],"total_price":100.0,"metadata":{},"created_by_reference":"33333333-3333-3333-3333-333333333333","updated_by_reference":"33333333-3333-3333-3333-333333333333","created_at_timestamp":"2026-01-01T09:30:00","updated_at_timestamp":"2026-01-01T09:30:00","selected_boxes":[{"lat_min":50.0,"lat_max":51.0,"lon_min":-2.0,"lon_max":-1.0,"coverage":100.0,"bath_value":10.0}]}],"selected_boxes":[{"lat_min":50.0,"lat_max":51.0,"lon_min":-2.0,"lon_max":-1.0,"coverage":100.0,"bath_value":10.0}],"discount":{},"total_price":200.0,"metadata":{},"created_by_reference":"33333333-3333-3333-3333-333333333333","updated_by_reference":"33333333-3333-3333-3333-333333333333","created_at_timestamp":"2026-01-01T09:30:00","updated_at_timestamp":"2026-01-01T09:30:00","original_price_before_discount":200.0,"applied_discount_value":0.0,"final_price_after_discount":200.0}}}},"401":{"description":"No token, an expired token, or not allowed to do this","content":{"application/json":{"schema":{"type":"object","properties":{"error_reference":{"type":"string","description":"Quote this when reporting a failure."},"error_type":{"type":"string"},"errors":{"type":"object","properties":{},"description":"Field name, or `message`, to a list of messages.","additionalProperties":true},"status_code":{"type":"integer"},"timestamp":{"type":"string"}},"required":["error_reference","error_type","errors","status_code","timestamp"],"description":"Every error from this service, whatever the cause. Rendered by InfrastructureError.","additionalProperties":true},"example":{"error_reference":"b7825cd3-59dc-43a9-9e1f-964676d3cf72","error_type":"NOT_FOUND_ERROR","errors":{"message":["Product with reference 11111111-1111-1111-1111-111111111111 not found."]},"status_code":401,"timestamp":"2026-09-30T10:00:00.000000"}}}},"404":{"description":"No such record","content":{"application/json":{"schema":{"type":"object","properties":{"error_reference":{"type":"string","description":"Quote this when reporting a failure."},"error_type":{"type":"string"},"errors":{"type":"object","properties":{},"description":"Field name, or `message`, to a list of messages.","additionalProperties":true},"status_code":{"type":"integer"},"timestamp":{"type":"string"}},"required":["error_reference","error_type","errors","status_code","timestamp"],"description":"Every error from this service, whatever the cause. Rendered by InfrastructureError.","additionalProperties":true},"example":{"error_reference":"b7825cd3-59dc-43a9-9e1f-964676d3cf72","error_type":"NOT_FOUND_ERROR","errors":{"message":["Product with reference 11111111-1111-1111-1111-111111111111 not found."]},"status_code":404,"timestamp":"2026-09-30T10:00:00.000000"}}}},"500":{"description":"Unexpected failure. Quote `error_reference` when reporting it","content":{"application/json":{"schema":{"type":"object","properties":{"error_reference":{"type":"string","description":"Quote this when reporting a failure."},"error_type":{"type":"string"},"errors":{"type":"object","properties":{},"description":"Field name, or `message`, to a list of messages.","additionalProperties":true},"status_code":{"type":"integer"},"timestamp":{"type":"string"}},"required":["error_reference","error_type","errors","status_code","timestamp"],"description":"Every error from this service, whatever the cause. Rendered by InfrastructureError.","additionalProperties":true},"example":{"error_reference":"b7825cd3-59dc-43a9-9e1f-964676d3cf72","error_type":"NOT_FOUND_ERROR","errors":{"message":["Product with reference 11111111-1111-1111-1111-111111111111 not found."]},"status_code":500,"timestamp":"2026-09-30T10:00:00.000000"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/products/{reference}/feature":{"post":{"tags":["Products"],"summary":"Create a feature on a product","description":"Creates the feature in the catalogue and adds it to the product.","operationId":"add_feature_to_product_products__reference__feature_post","security":[{"HTTPBearer":[]}],"parameters":[{"name":"reference","in":"path","required":true,"schema":{"type":"string","title":"Reference"}}],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"type":"object","properties":{"reference":{"type":"string"}},"required":["reference"],"description":"The reference of the record that was created, changed or removed.","additionalProperties":true},"example":{"reference":"11111111-1111-1111-1111-111111111111"}}}},"400":{"description":"The body or a value in it is not valid","content":{"application/json":{"schema":{"type":"object","properties":{"error_reference":{"type":"string","description":"Quote this when reporting a failure."},"error_type":{"type":"string"},"errors":{"type":"object","properties":{},"description":"Field name, or `message`, to a list of messages.","additionalProperties":true},"status_code":{"type":"integer"},"timestamp":{"type":"string"}},"required":["error_reference","error_type","errors","status_code","timestamp"],"description":"Every error from this service, whatever the cause. Rendered by InfrastructureError.","additionalProperties":true},"example":{"error_reference":"b7825cd3-59dc-43a9-9e1f-964676d3cf72","error_type":"NOT_FOUND_ERROR","errors":{"message":["Product with reference 11111111-1111-1111-1111-111111111111 not found."]},"status_code":400,"timestamp":"2026-09-30T10:00:00.000000"}}}},"401":{"description":"No token, an expired token, or not allowed to do this","content":{"application/json":{"schema":{"type":"object","properties":{"error_reference":{"type":"string","description":"Quote this when reporting a failure."},"error_type":{"type":"string"},"errors":{"type":"object","properties":{},"description":"Field name, or `message`, to a list of messages.","additionalProperties":true},"status_code":{"type":"integer"},"timestamp":{"type":"string"}},"required":["error_reference","error_type","errors","status_code","timestamp"],"description":"Every error from this service, whatever the cause. Rendered by InfrastructureError.","additionalProperties":true},"example":{"error_reference":"b7825cd3-59dc-43a9-9e1f-964676d3cf72","error_type":"NOT_FOUND_ERROR","errors":{"message":["Product with reference 11111111-1111-1111-1111-111111111111 not found."]},"status_code":401,"timestamp":"2026-09-30T10:00:00.000000"}}}},"404":{"description":"No such record","content":{"application/json":{"schema":{"type":"object","properties":{"error_reference":{"type":"string","description":"Quote this when reporting a failure."},"error_type":{"type":"string"},"errors":{"type":"object","properties":{},"description":"Field name, or `message`, to a list of messages.","additionalProperties":true},"status_code":{"type":"integer"},"timestamp":{"type":"string"}},"required":["error_reference","error_type","errors","status_code","timestamp"],"description":"Every error from this service, whatever the cause. Rendered by InfrastructureError.","additionalProperties":true},"example":{"error_reference":"b7825cd3-59dc-43a9-9e1f-964676d3cf72","error_type":"NOT_FOUND_ERROR","errors":{"message":["Product with reference 11111111-1111-1111-1111-111111111111 not found."]},"status_code":404,"timestamp":"2026-09-30T10:00:00.000000"}}}},"409":{"description":"A record with that name already exists","content":{"application/json":{"schema":{"type":"object","properties":{"error_reference":{"type":"string","description":"Quote this when reporting a failure."},"error_type":{"type":"string"},"errors":{"type":"object","properties":{},"description":"Field name, or `message`, to a list of messages.","additionalProperties":true},"status_code":{"type":"integer"},"timestamp":{"type":"string"}},"required":["error_reference","error_type","errors","status_code","timestamp"],"description":"Every error from this service, whatever the cause. Rendered by InfrastructureError.","additionalProperties":true},"example":{"error_reference":"b7825cd3-59dc-43a9-9e1f-964676d3cf72","error_type":"NOT_FOUND_ERROR","errors":{"message":["Product with reference 11111111-1111-1111-1111-111111111111 not found."]},"status_code":409,"timestamp":"2026-09-30T10:00:00.000000"}}}},"500":{"description":"Unexpected failure. Quote `error_reference` when reporting it","content":{"application/json":{"schema":{"type":"object","properties":{"error_reference":{"type":"string","description":"Quote this when reporting a failure."},"error_type":{"type":"string"},"errors":{"type":"object","properties":{},"description":"Field name, or `message`, to a list of messages.","additionalProperties":true},"status_code":{"type":"integer"},"timestamp":{"type":"string"}},"required":["error_reference","error_type","errors","status_code","timestamp"],"description":"Every error from this service, whatever the cause. Rendered by InfrastructureError.","additionalProperties":true},"example":{"error_reference":"b7825cd3-59dc-43a9-9e1f-964676d3cf72","error_type":"NOT_FOUND_ERROR","errors":{"message":["Product with reference 11111111-1111-1111-1111-111111111111 not found."]},"status_code":500,"timestamp":"2026-09-30T10:00:00.000000"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}},"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"feature_name":{"type":"string"},"description":{"type":"string"},"feature_type":{"type":"string","description":"For example `model`, `port` or `data`."},"base_price":{"type":"number"},"price_multiplier":{"type":["number","null"],"description":"Price per extra box. Model features only; defaults to 0."},"prediction":{"type":["integer","null"],"description":"Model features only; defaults to 1."},"hindcast":{"type":["integer","null"],"description":"Model features only; defaults to 1."},"box_size":{"type":["string","null"],"description":"`NxN` in degrees. Model features only; defaults to `1x1`."},"min_boxes":{"type":["integer","null"],"description":"Model features only; defaults to 1."},"boxes":{"type":"array","description":"Geographic boxes.","items":{"type":"object","properties":{"lat_min":{"type":"number"},"lat_max":{"type":"number"},"lon_min":{"type":"number"},"lon_max":{"type":"number"},"coverage":{"type":"number"},"bath_value":{"type":"number"}},"additionalProperties":true}},"metadata":{"type":"object","additionalProperties":true},"created_at_timestamp":{"type":"string","description":"Optional. Generated by the service when left out."},"updated_at_timestamp":{"type":"string","description":"Optional. Generated by the service when left out."},"reference":{"type":"string","description":"Optional. Generated by the service when left out."}},"required":["feature_name","description","feature_type","base_price"]},"example":{"feature_name":"Tidal streams","description":"A catalogue feature","feature_type":"model","base_price":100.0,"box_size":"1x1","price_multiplier":1.0,"min_boxes":1,"boxes":[{"lat_min":50.0,"lat_max":51.0,"lon_min":-2.0,"lon_max":-1.0,"coverage":100.0,"bath_value":10.0}]}}}}}},"/products/{reference}/features":{"post":{"tags":["Products"],"summary":"Add existing features to a product","operationId":"add_features_to_product_products__reference__features_post","security":[{"HTTPBearer":[]}],"parameters":[{"name":"reference","in":"path","required":true,"schema":{"type":"string","title":"Reference"}}],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"type":"object","properties":{"reference":{"type":"string"}},"required":["reference"],"description":"The reference of the record that was created, changed or removed.","additionalProperties":true},"example":{"reference":"11111111-1111-1111-1111-111111111111"}}}},"400":{"description":"The body or a value in it is not valid","content":{"application/json":{"schema":{"type":"object","properties":{"error_reference":{"type":"string","description":"Quote this when reporting a failure."},"error_type":{"type":"string"},"errors":{"type":"object","properties":{},"description":"Field name, or `message`, to a list of messages.","additionalProperties":true},"status_code":{"type":"integer"},"timestamp":{"type":"string"}},"required":["error_reference","error_type","errors","status_code","timestamp"],"description":"Every error from this service, whatever the cause. Rendered by InfrastructureError.","additionalProperties":true},"example":{"error_reference":"b7825cd3-59dc-43a9-9e1f-964676d3cf72","error_type":"NOT_FOUND_ERROR","errors":{"message":["Product with reference 11111111-1111-1111-1111-111111111111 not found."]},"status_code":400,"timestamp":"2026-09-30T10:00:00.000000"}}}},"401":{"description":"No token, an expired token, or not allowed to do this","content":{"application/json":{"schema":{"type":"object","properties":{"error_reference":{"type":"string","description":"Quote this when reporting a failure."},"error_type":{"type":"string"},"errors":{"type":"object","properties":{},"description":"Field name, or `message`, to a list of messages.","additionalProperties":true},"status_code":{"type":"integer"},"timestamp":{"type":"string"}},"required":["error_reference","error_type","errors","status_code","timestamp"],"description":"Every error from this service, whatever the cause. Rendered by InfrastructureError.","additionalProperties":true},"example":{"error_reference":"b7825cd3-59dc-43a9-9e1f-964676d3cf72","error_type":"NOT_FOUND_ERROR","errors":{"message":["Product with reference 11111111-1111-1111-1111-111111111111 not found."]},"status_code":401,"timestamp":"2026-09-30T10:00:00.000000"}}}},"404":{"description":"No such record","content":{"application/json":{"schema":{"type":"object","properties":{"error_reference":{"type":"string","description":"Quote this when reporting a failure."},"error_type":{"type":"string"},"errors":{"type":"object","properties":{},"description":"Field name, or `message`, to a list of messages.","additionalProperties":true},"status_code":{"type":"integer"},"timestamp":{"type":"string"}},"required":["error_reference","error_type","errors","status_code","timestamp"],"description":"Every error from this service, whatever the cause. Rendered by InfrastructureError.","additionalProperties":true},"example":{"error_reference":"b7825cd3-59dc-43a9-9e1f-964676d3cf72","error_type":"NOT_FOUND_ERROR","errors":{"message":["Product with reference 11111111-1111-1111-1111-111111111111 not found."]},"status_code":404,"timestamp":"2026-09-30T10:00:00.000000"}}}},"500":{"description":"Unexpected failure. Quote `error_reference` when reporting it","content":{"application/json":{"schema":{"type":"object","properties":{"error_reference":{"type":"string","description":"Quote this when reporting a failure."},"error_type":{"type":"string"},"errors":{"type":"object","properties":{},"description":"Field name, or `message`, to a list of messages.","additionalProperties":true},"status_code":{"type":"integer"},"timestamp":{"type":"string"}},"required":["error_reference","error_type","errors","status_code","timestamp"],"description":"Every error from this service, whatever the cause. Rendered by InfrastructureError.","additionalProperties":true},"example":{"error_reference":"b7825cd3-59dc-43a9-9e1f-964676d3cf72","error_type":"NOT_FOUND_ERROR","errors":{"message":["Product with reference 11111111-1111-1111-1111-111111111111 not found."]},"status_code":500,"timestamp":"2026-09-30T10:00:00.000000"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}},"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"references":{"type":"array","description":"Feature references.","items":{"type":"string"}}},"required":["references"]},"example":{"references":["22222222-2222-2222-2222-222222222222"]}}}}},"get":{"tags":["Products"],"summary":"A product's features","description":"Paged, ten to a page. Any field of the record can be used as a filter\n(`?field=value`), not only the ones listed; text is matched partially and case-insensitively.\n`all` searches across the text fields instead. `ordering=field` sorts ascending,\n`ordering=-field` descending; the default is newest first.","operationId":"get_product_features_products__reference__features_get","parameters":[{"name":"reference","in":"path","required":true,"schema":{"type":"string","title":"Reference"}},{"name":"page","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Page number.","examples":["1"],"title":"Page"},"description":"Page number."},{"name":"ordering","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"`field` ascending, `-field` descending. Default `-created_at_timestamp`.","examples":["feature_name"],"title":"Ordering"},"description":"`field` ascending, `-field` descending. Default `-created_at_timestamp`."},{"name":"all","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Search across the text fields.","title":"All"},"description":"Search across the text fields."},{"name":"feature_name","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Partial, case-insensitive.","title":"Feature Name"},"description":"Partial, case-insensitive."},{"name":"feature_type","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"For example `model`, `port` or `data`.","title":"Feature Type"},"description":"For example `model`, `port` or `data`."}],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"type":"object","properties":{"items":{"type":"array","items":{"type":"object","properties":{"reference":{"type":"string"},"feature_name":{"type":"string"},"description":{"type":["string","null"]},"feature_type":{"type":"string","description":"For example `model`, `port` or `data`."},"base_price":{"type":"number"},"prediction":{"type":["integer","null"],"description":"Model features only; null otherwise."},"hindcast":{"type":["integer","null"],"description":"Model features only; null otherwise."},"box_size":{"type":["string","null"],"description":"`NxN` in degrees. Model features only."},"price_multiplier":{"type":["number","null"],"description":"Price per extra box. Model features only."},"min_boxes":{"type":"integer"},"boxes":{"type":"array","description":"Geographic boxes. Model features only.","items":{"type":"object","properties":{"lat_min":{"type":"number"},"lat_max":{"type":"number"},"lon_min":{"type":"number"},"lon_max":{"type":"number"},"coverage":{"type":"number"},"bath_value":{"type":"number"}},"additionalProperties":true}},"total_price":{"type":"number"},"metadata":{"type":"object","additionalProperties":true},"created_by_reference":{"type":["string","null"]},"updated_by_reference":{"type":["string","null"]},"created_at_timestamp":{"type":["string","null"]},"updated_at_timestamp":{"type":["string","null"]}},"required":["reference","feature_name","description","feature_type","base_price","prediction","hindcast","box_size","price_multiplier","min_boxes","boxes","total_price","metadata","created_by_reference","updated_by_reference","created_at_timestamp","updated_at_timestamp"],"description":"A catalogue feature, as `Feature.to_dict` renders it.","additionalProperties":true}},"pagination":{"type":"object","properties":{"current_page":{"type":"integer"},"page_size":{"type":"integer"},"total_records":{"type":"integer"},"total_pages":{"type":"integer"},"has_next":{"type":"boolean"},"has_previous":{"type":"boolean"}},"required":["current_page","page_size","total_records","total_pages","has_next","has_previous"],"additionalProperties":true}},"required":["items","pagination"],"additionalProperties":true},"example":{"items":[{"reference":"22222222-2222-2222-2222-222222222222","feature_name":"Tidal streams","description":"A catalogue feature","feature_type":"model","base_price":100.0,"prediction":1,"hindcast":1,"box_size":"1x1","price_multiplier":1.0,"min_boxes":1,"boxes":[{"lat_min":50.0,"lat_max":51.0,"lon_min":-2.0,"lon_max":-1.0,"coverage":100.0,"bath_value":10.0}],"total_price":100.0,"metadata":{},"created_by_reference":"33333333-3333-3333-3333-333333333333","updated_by_reference":"33333333-3333-3333-3333-333333333333","created_at_timestamp":"2026-01-01T09:30:00","updated_at_timestamp":"2026-01-01T09:30:00"}],"pagination":{"current_page":1,"page_size":10,"total_records":1,"total_pages":1,"has_next":false,"has_previous":false}}}}},"404":{"description":"No such record","content":{"application/json":{"schema":{"type":"object","properties":{"error_reference":{"type":"string","description":"Quote this when reporting a failure."},"error_type":{"type":"string"},"errors":{"type":"object","properties":{},"description":"Field name, or `message`, to a list of messages.","additionalProperties":true},"status_code":{"type":"integer"},"timestamp":{"type":"string"}},"required":["error_reference","error_type","errors","status_code","timestamp"],"description":"Every error from this service, whatever the cause. Rendered by InfrastructureError.","additionalProperties":true},"example":{"error_reference":"b7825cd3-59dc-43a9-9e1f-964676d3cf72","error_type":"NOT_FOUND_ERROR","errors":{"message":["Product with reference 11111111-1111-1111-1111-111111111111 not found."]},"status_code":404,"timestamp":"2026-09-30T10:00:00.000000"}}}},"500":{"description":"Unexpected failure. Quote `error_reference` when reporting it","content":{"application/json":{"schema":{"type":"object","properties":{"error_reference":{"type":"string","description":"Quote this when reporting a failure."},"error_type":{"type":"string"},"errors":{"type":"object","properties":{},"description":"Field name, or `message`, to a list of messages.","additionalProperties":true},"status_code":{"type":"integer"},"timestamp":{"type":"string"}},"required":["error_reference","error_type","errors","status_code","timestamp"],"description":"Every error from this service, whatever the cause. Rendered by InfrastructureError.","additionalProperties":true},"example":{"error_reference":"b7825cd3-59dc-43a9-9e1f-964676d3cf72","error_type":"NOT_FOUND_ERROR","errors":{"message":["Product with reference 11111111-1111-1111-1111-111111111111 not found."]},"status_code":500,"timestamp":"2026-09-30T10:00:00.000000"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/products/{reference}/bundles":{"post":{"tags":["Bundles"],"summary":"Create a bundle on a product","description":"The name must be unique. Each feature takes only the selected boxes that are its own, and must reach its `min_boxes`.","operationId":"create_product_bundle_products__reference__bundles_post","security":[{"HTTPBearer":[]}],"parameters":[{"name":"reference","in":"path","required":true,"schema":{"type":"string","title":"Reference"}}],"responses":{"201":{"description":"Created","content":{"application/json":{"schema":{"type":"object","properties":{"reference":{"type":"string"}},"required":["reference"],"description":"The reference of the record that was created, changed or removed.","additionalProperties":true},"example":{"reference":"11111111-1111-1111-1111-111111111111"}}}},"400":{"description":"The body or a value in it is not valid","content":{"application/json":{"schema":{"type":"object","properties":{"error_reference":{"type":"string","description":"Quote this when reporting a failure."},"error_type":{"type":"string"},"errors":{"type":"object","properties":{},"description":"Field name, or `message`, to a list of messages.","additionalProperties":true},"status_code":{"type":"integer"},"timestamp":{"type":"string"}},"required":["error_reference","error_type","errors","status_code","timestamp"],"description":"Every error from this service, whatever the cause. Rendered by InfrastructureError.","additionalProperties":true},"example":{"error_reference":"b7825cd3-59dc-43a9-9e1f-964676d3cf72","error_type":"NOT_FOUND_ERROR","errors":{"message":["Product with reference 11111111-1111-1111-1111-111111111111 not found."]},"status_code":400,"timestamp":"2026-09-30T10:00:00.000000"}}}},"401":{"description":"No token, an expired token, or not allowed to do this","content":{"application/json":{"schema":{"type":"object","properties":{"error_reference":{"type":"string","description":"Quote this when reporting a failure."},"error_type":{"type":"string"},"errors":{"type":"object","properties":{},"description":"Field name, or `message`, to a list of messages.","additionalProperties":true},"status_code":{"type":"integer"},"timestamp":{"type":"string"}},"required":["error_reference","error_type","errors","status_code","timestamp"],"description":"Every error from this service, whatever the cause. Rendered by InfrastructureError.","additionalProperties":true},"example":{"error_reference":"b7825cd3-59dc-43a9-9e1f-964676d3cf72","error_type":"NOT_FOUND_ERROR","errors":{"message":["Product with reference 11111111-1111-1111-1111-111111111111 not found."]},"status_code":401,"timestamp":"2026-09-30T10:00:00.000000"}}}},"404":{"description":"No such record","content":{"application/json":{"schema":{"type":"object","properties":{"error_reference":{"type":"string","description":"Quote this when reporting a failure."},"error_type":{"type":"string"},"errors":{"type":"object","properties":{},"description":"Field name, or `message`, to a list of messages.","additionalProperties":true},"status_code":{"type":"integer"},"timestamp":{"type":"string"}},"required":["error_reference","error_type","errors","status_code","timestamp"],"description":"Every error from this service, whatever the cause. Rendered by InfrastructureError.","additionalProperties":true},"example":{"error_reference":"b7825cd3-59dc-43a9-9e1f-964676d3cf72","error_type":"NOT_FOUND_ERROR","errors":{"message":["Product with reference 11111111-1111-1111-1111-111111111111 not found."]},"status_code":404,"timestamp":"2026-09-30T10:00:00.000000"}}}},"409":{"description":"A record with that name already exists","content":{"application/json":{"schema":{"type":"object","properties":{"error_reference":{"type":"string","description":"Quote this when reporting a failure."},"error_type":{"type":"string"},"errors":{"type":"object","properties":{},"description":"Field name, or `message`, to a list of messages.","additionalProperties":true},"status_code":{"type":"integer"},"timestamp":{"type":"string"}},"required":["error_reference","error_type","errors","status_code","timestamp"],"description":"Every error from this service, whatever the cause. Rendered by InfrastructureError.","additionalProperties":true},"example":{"error_reference":"b7825cd3-59dc-43a9-9e1f-964676d3cf72","error_type":"NOT_FOUND_ERROR","errors":{"message":["Product with reference 11111111-1111-1111-1111-111111111111 not found."]},"status_code":409,"timestamp":"2026-09-30T10:00:00.000000"}}}},"500":{"description":"Unexpected failure. Quote `error_reference` when reporting it","content":{"application/json":{"schema":{"type":"object","properties":{"error_reference":{"type":"string","description":"Quote this when reporting a failure."},"error_type":{"type":"string"},"errors":{"type":"object","properties":{},"description":"Field name, or `message`, to a list of messages.","additionalProperties":true},"status_code":{"type":"integer"},"timestamp":{"type":"string"}},"required":["error_reference","error_type","errors","status_code","timestamp"],"description":"Every error from this service, whatever the cause. Rendered by InfrastructureError.","additionalProperties":true},"example":{"error_reference":"b7825cd3-59dc-43a9-9e1f-964676d3cf72","error_type":"NOT_FOUND_ERROR","errors":{"message":["Product with reference 11111111-1111-1111-1111-111111111111 not found."]},"status_code":500,"timestamp":"2026-09-30T10:00:00.000000"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}},"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"bundle_name":{"type":"string"},"bundle_type":{"type":"string","description":"For example `user` or `organisation`."},"description":{"type":"string"},"license_reference":{"type":"string"},"feature_references":{"type":"array","items":{"type":"string"}},"selected_boxes":{"type":"array","description":"Boxes picked from the features' own boxes.","items":{"type":"object","properties":{"lat_min":{"type":"number"},"lat_max":{"type":"number"},"lon_min":{"type":"number"},"lon_max":{"type":"number"},"coverage":{"type":"number"},"bath_value":{"type":"number"}},"additionalProperties":true}},"discount_reference":{"type":["string","null"]},"base_price":{"type":"number"},"metadata":{"type":"object","additionalProperties":true},"reference":{"type":"string","description":"Optional. Generated by the service when left out."},"created_at_timestamp":{"type":"string","description":"Optional. Generated by the service when left out."},"updated_at_timestamp":{"type":"string","description":"Optional. Generated by the service when left out."}},"required":["bundle_name","bundle_type","description","license_reference"]},"example":{"bundle_name":"Premium ports","bundle_type":"user","description":"A premium ports bundle","license_reference":"55555555-5555-5555-5555-555555555555","feature_references":["22222222-2222-2222-2222-222222222222"],"selected_boxes":[{"lat_min":50.0,"lat_max":51.0,"lon_min":-2.0,"lon_max":-1.0,"coverage":100.0,"bath_value":10.0}],"base_price":100.0}}}}},"get":{"tags":["Bundles"],"summary":"A product's bundles","operationId":"get_all_product_bundles_products__reference__bundles_get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"reference","in":"path","required":true,"schema":{"type":"string","title":"Reference"}},{"name":"page","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Page number. `all` turns paging off.","examples":["1"],"title":"Page"},"description":"Page number. `all` turns paging off."},{"name":"ordering","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"`field` ascending, `-field` descending. Default `-created_at_timestamp`.","examples":["bundle_name"],"title":"Ordering"},"description":"`field` ascending, `-field` descending. Default `-created_at_timestamp`."}],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"type":"object","properties":{"items":{"type":"array","items":{"type":"object","properties":{"reference":{"type":"string"},"product_reference":{"type":"string"},"product":{"type":"object","description":"The product's name and description, copied onto the bundle.","additionalProperties":true},"bundle_name":{"type":"string"},"bundle_type":{"type":"string","description":"For example `user` or `organisation`."},"description":{"type":["string","null"]},"base_price":{"type":"number"},"license":{"type":"object","description":"The licence record the bundle carries, or `{}`.","additionalProperties":true},"features":{"type":"array","description":"Feature records, each with the `selected_boxes` that belong to it.","items":{"type":"object","additionalProperties":true}},"selected_boxes":{"type":"array","items":{"type":"object","properties":{"lat_min":{"type":"number"},"lat_max":{"type":"number"},"lon_min":{"type":"number"},"lon_max":{"type":"number"},"coverage":{"type":"number"},"bath_value":{"type":"number"}},"additionalProperties":true}},"discount":{"type":"object","description":"The discount record the bundle carries, or `{}`.","additionalProperties":true},"total_price":{"type":"number"},"metadata":{"type":"object","additionalProperties":true},"created_by_reference":{"type":["string","null"]},"updated_by_reference":{"type":["string","null"]},"created_at_timestamp":{"type":["string","null"]},"updated_at_timestamp":{"type":["string","null"]},"original_price_before_discount":{"type":"number"},"applied_discount_value":{"type":"number"},"final_price_after_discount":{"type":"number"}},"required":["reference","product_reference","product","bundle_name","bundle_type","description","base_price","license","features","selected_boxes","discount","total_price","metadata","created_by_reference","updated_by_reference","created_at_timestamp","updated_at_timestamp","original_price_before_discount","applied_discount_value","final_price_after_discount"],"description":"A bundle, as `Bundle.to_dict` renders it, with its price worked out.","additionalProperties":true}},"pagination":{"type":"object","properties":{"current_page":{"type":"integer"},"page_size":{"type":"integer"},"total_records":{"type":"integer"},"total_pages":{"type":"integer"},"has_next":{"type":"boolean"},"has_previous":{"type":"boolean"}},"required":["current_page","page_size","total_records","total_pages","has_next","has_previous"],"additionalProperties":true}},"required":["items","pagination"],"additionalProperties":true},"example":{"items":[{"reference":"44444444-4444-4444-4444-444444444444","product_reference":"11111111-1111-1111-1111-111111111111","product":{"product_name":"Ports","description":"Port prediction product"},"bundle_name":"Premium ports","bundle_type":"user","description":"A premium ports bundle","base_price":100.0,"license":{"reference":"55555555-5555-5555-5555-555555555555","license_type":"Premium","max_users":5,"multiplier":1.0,"duration":12,"metadata":{}},"features":[{"reference":"22222222-2222-2222-2222-222222222222","feature_name":"Tidal streams","description":"A catalogue feature","feature_type":"model","base_price":100.0,"prediction":1,"hindcast":1,"box_size":"1x1","price_multiplier":1.0,"min_boxes":1,"boxes":[{"lat_min":50.0,"lat_max":51.0,"lon_min":-2.0,"lon_max":-1.0,"coverage":100.0,"bath_value":10.0}],"total_price":100.0,"metadata":{},"created_by_reference":"33333333-3333-3333-3333-333333333333","updated_by_reference":"33333333-3333-3333-3333-333333333333","created_at_timestamp":"2026-01-01T09:30:00","updated_at_timestamp":"2026-01-01T09:30:00","selected_boxes":[{"lat_min":50.0,"lat_max":51.0,"lon_min":-2.0,"lon_max":-1.0,"coverage":100.0,"bath_value":10.0}]}],"selected_boxes":[{"lat_min":50.0,"lat_max":51.0,"lon_min":-2.0,"lon_max":-1.0,"coverage":100.0,"bath_value":10.0}],"discount":{},"total_price":200.0,"metadata":{},"created_by_reference":"33333333-3333-3333-3333-333333333333","updated_by_reference":"33333333-3333-3333-3333-333333333333","created_at_timestamp":"2026-01-01T09:30:00","updated_at_timestamp":"2026-01-01T09:30:00","original_price_before_discount":200.0,"applied_discount_value":0.0,"final_price_after_discount":200.0}],"pagination":{"current_page":1,"page_size":10,"total_records":1,"total_pages":1,"has_next":false,"has_previous":false}}}}},"401":{"description":"No token, an expired token, or not allowed to do this","content":{"application/json":{"schema":{"type":"object","properties":{"error_reference":{"type":"string","description":"Quote this when reporting a failure."},"error_type":{"type":"string"},"errors":{"type":"object","properties":{},"description":"Field name, or `message`, to a list of messages.","additionalProperties":true},"status_code":{"type":"integer"},"timestamp":{"type":"string"}},"required":["error_reference","error_type","errors","status_code","timestamp"],"description":"Every error from this service, whatever the cause. Rendered by InfrastructureError.","additionalProperties":true},"example":{"error_reference":"b7825cd3-59dc-43a9-9e1f-964676d3cf72","error_type":"NOT_FOUND_ERROR","errors":{"message":["Product with reference 11111111-1111-1111-1111-111111111111 not found."]},"status_code":401,"timestamp":"2026-09-30T10:00:00.000000"}}}},"500":{"description":"Unexpected failure. Quote `error_reference` when reporting it","content":{"application/json":{"schema":{"type":"object","properties":{"error_reference":{"type":"string","description":"Quote this when reporting a failure."},"error_type":{"type":"string"},"errors":{"type":"object","properties":{},"description":"Field name, or `message`, to a list of messages.","additionalProperties":true},"status_code":{"type":"integer"},"timestamp":{"type":"string"}},"required":["error_reference","error_type","errors","status_code","timestamp"],"description":"Every error from this service, whatever the cause. Rendered by InfrastructureError.","additionalProperties":true},"example":{"error_reference":"b7825cd3-59dc-43a9-9e1f-964676d3cf72","error_type":"NOT_FOUND_ERROR","errors":{"message":["Product with reference 11111111-1111-1111-1111-111111111111 not found."]},"status_code":500,"timestamp":"2026-09-30T10:00:00.000000"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/products/{reference}":{"get":{"tags":["Products"],"summary":"One product","operationId":"get_product_by_reference_products__reference__get","parameters":[{"name":"reference","in":"path","required":true,"schema":{"type":"string","title":"Reference"}}],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"type":"object","properties":{"reference":{"type":"string"},"product_name":{"type":"string"},"description":{"type":["string","null"]},"features":{"type":"array","description":"The product's features, each shaped like a feature record.","items":{"type":"object","additionalProperties":true}},"metadata":{"type":"object","additionalProperties":true},"created_by_reference":{"type":["string","null"]},"updated_by_reference":{"type":["string","null"]},"created_at_timestamp":{"type":["string","null"]},"updated_at_timestamp":{"type":["string","null"]}},"required":["reference","product_name","description","features","metadata","created_by_reference","updated_by_reference","created_at_timestamp","updated_at_timestamp"],"description":"A product, as `Product.to_dict` renders it.","additionalProperties":true},"example":{"reference":"11111111-1111-1111-1111-111111111111","product_name":"Ports","description":"Port prediction product","features":[{"reference":"22222222-2222-2222-2222-222222222222","feature_name":"Tidal streams","description":"A catalogue feature","feature_type":"model","base_price":100.0,"prediction":1,"hindcast":1,"box_size":"1x1","price_multiplier":1.0,"min_boxes":1,"boxes":[{"lat_min":50.0,"lat_max":51.0,"lon_min":-2.0,"lon_max":-1.0,"coverage":100.0,"bath_value":10.0}],"total_price":100.0,"metadata":{},"created_by_reference":"33333333-3333-3333-3333-333333333333","updated_by_reference":"33333333-3333-3333-3333-333333333333","created_at_timestamp":"2026-01-01T09:30:00","updated_at_timestamp":"2026-01-01T09:30:00"}],"metadata":{},"created_by_reference":"33333333-3333-3333-3333-333333333333","updated_by_reference":"33333333-3333-3333-3333-333333333333","created_at_timestamp":"2026-01-01T09:30:00","updated_at_timestamp":"2026-01-01T09:30:00"}}}},"404":{"description":"No such record","content":{"application/json":{"schema":{"type":"object","properties":{"error_reference":{"type":"string","description":"Quote this when reporting a failure."},"error_type":{"type":"string"},"errors":{"type":"object","properties":{},"description":"Field name, or `message`, to a list of messages.","additionalProperties":true},"status_code":{"type":"integer"},"timestamp":{"type":"string"}},"required":["error_reference","error_type","errors","status_code","timestamp"],"description":"Every error from this service, whatever the cause. Rendered by InfrastructureError.","additionalProperties":true},"example":{"error_reference":"b7825cd3-59dc-43a9-9e1f-964676d3cf72","error_type":"NOT_FOUND_ERROR","errors":{"message":["Product with reference 11111111-1111-1111-1111-111111111111 not found."]},"status_code":404,"timestamp":"2026-09-30T10:00:00.000000"}}}},"500":{"description":"Unexpected failure. Quote `error_reference` when reporting it","content":{"application/json":{"schema":{"type":"object","properties":{"error_reference":{"type":"string","description":"Quote this when reporting a failure."},"error_type":{"type":"string"},"errors":{"type":"object","properties":{},"description":"Field name, or `message`, to a list of messages.","additionalProperties":true},"status_code":{"type":"integer"},"timestamp":{"type":"string"}},"required":["error_reference","error_type","errors","status_code","timestamp"],"description":"Every error from this service, whatever the cause. Rendered by InfrastructureError.","additionalProperties":true},"example":{"error_reference":"b7825cd3-59dc-43a9-9e1f-964676d3cf72","error_type":"NOT_FOUND_ERROR","errors":{"message":["Product with reference 11111111-1111-1111-1111-111111111111 not found."]},"status_code":500,"timestamp":"2026-09-30T10:00:00.000000"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"put":{"tags":["Products"],"summary":"Update a product","description":"Partial: send only what changes.","operationId":"update_product_products__reference__put","security":[{"HTTPBearer":[]}],"parameters":[{"name":"reference","in":"path","required":true,"schema":{"type":"string","title":"Reference"}}],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"type":"object","properties":{"reference":{"type":"string"}},"required":["reference"],"description":"The reference of the record that was created, changed or removed.","additionalProperties":true},"example":{"reference":"11111111-1111-1111-1111-111111111111"}}}},"400":{"description":"The body or a value in it is not valid","content":{"application/json":{"schema":{"type":"object","properties":{"error_reference":{"type":"string","description":"Quote this when reporting a failure."},"error_type":{"type":"string"},"errors":{"type":"object","properties":{},"description":"Field name, or `message`, to a list of messages.","additionalProperties":true},"status_code":{"type":"integer"},"timestamp":{"type":"string"}},"required":["error_reference","error_type","errors","status_code","timestamp"],"description":"Every error from this service, whatever the cause. Rendered by InfrastructureError.","additionalProperties":true},"example":{"error_reference":"b7825cd3-59dc-43a9-9e1f-964676d3cf72","error_type":"NOT_FOUND_ERROR","errors":{"message":["Product with reference 11111111-1111-1111-1111-111111111111 not found."]},"status_code":400,"timestamp":"2026-09-30T10:00:00.000000"}}}},"401":{"description":"No token, an expired token, or not allowed to do this","content":{"application/json":{"schema":{"type":"object","properties":{"error_reference":{"type":"string","description":"Quote this when reporting a failure."},"error_type":{"type":"string"},"errors":{"type":"object","properties":{},"description":"Field name, or `message`, to a list of messages.","additionalProperties":true},"status_code":{"type":"integer"},"timestamp":{"type":"string"}},"required":["error_reference","error_type","errors","status_code","timestamp"],"description":"Every error from this service, whatever the cause. Rendered by InfrastructureError.","additionalProperties":true},"example":{"error_reference":"b7825cd3-59dc-43a9-9e1f-964676d3cf72","error_type":"NOT_FOUND_ERROR","errors":{"message":["Product with reference 11111111-1111-1111-1111-111111111111 not found."]},"status_code":401,"timestamp":"2026-09-30T10:00:00.000000"}}}},"404":{"description":"No such record","content":{"application/json":{"schema":{"type":"object","properties":{"error_reference":{"type":"string","description":"Quote this when reporting a failure."},"error_type":{"type":"string"},"errors":{"type":"object","properties":{},"description":"Field name, or `message`, to a list of messages.","additionalProperties":true},"status_code":{"type":"integer"},"timestamp":{"type":"string"}},"required":["error_reference","error_type","errors","status_code","timestamp"],"description":"Every error from this service, whatever the cause. Rendered by InfrastructureError.","additionalProperties":true},"example":{"error_reference":"b7825cd3-59dc-43a9-9e1f-964676d3cf72","error_type":"NOT_FOUND_ERROR","errors":{"message":["Product with reference 11111111-1111-1111-1111-111111111111 not found."]},"status_code":404,"timestamp":"2026-09-30T10:00:00.000000"}}}},"409":{"description":"A record with that name already exists","content":{"application/json":{"schema":{"type":"object","properties":{"error_reference":{"type":"string","description":"Quote this when reporting a failure."},"error_type":{"type":"string"},"errors":{"type":"object","properties":{},"description":"Field name, or `message`, to a list of messages.","additionalProperties":true},"status_code":{"type":"integer"},"timestamp":{"type":"string"}},"required":["error_reference","error_type","errors","status_code","timestamp"],"description":"Every error from this service, whatever the cause. Rendered by InfrastructureError.","additionalProperties":true},"example":{"error_reference":"b7825cd3-59dc-43a9-9e1f-964676d3cf72","error_type":"NOT_FOUND_ERROR","errors":{"message":["Product with reference 11111111-1111-1111-1111-111111111111 not found."]},"status_code":409,"timestamp":"2026-09-30T10:00:00.000000"}}}},"500":{"description":"Unexpected failure. Quote `error_reference` when reporting it","content":{"application/json":{"schema":{"type":"object","properties":{"error_reference":{"type":"string","description":"Quote this when reporting a failure."},"error_type":{"type":"string"},"errors":{"type":"object","properties":{},"description":"Field name, or `message`, to a list of messages.","additionalProperties":true},"status_code":{"type":"integer"},"timestamp":{"type":"string"}},"required":["error_reference","error_type","errors","status_code","timestamp"],"description":"Every error from this service, whatever the cause. Rendered by InfrastructureError.","additionalProperties":true},"example":{"error_reference":"b7825cd3-59dc-43a9-9e1f-964676d3cf72","error_type":"NOT_FOUND_ERROR","errors":{"message":["Product with reference 11111111-1111-1111-1111-111111111111 not found."]},"status_code":500,"timestamp":"2026-09-30T10:00:00.000000"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}},"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"product_name":{"type":["string","null"]},"description":{"type":["string","null"]},"features":{"type":["array","null"],"items":{"type":"object","additionalProperties":true}},"metadata":{"type":["object","null"],"additionalProperties":true},"updated_at_timestamp":{"type":"string","description":"Optional. Generated by the service when left out."}},"description":"Send only what changes."},"example":{"description":"Updated description"}}}}},"delete":{"tags":["Products"],"summary":"Delete a product","operationId":"delete_product_products__reference__delete","security":[{"HTTPBearer":[]}],"parameters":[{"name":"reference","in":"path","required":true,"schema":{"type":"string","title":"Reference"}}],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"type":"object","properties":{"reference":{"type":"string"}},"required":["reference"],"description":"The reference of the record that was created, changed or removed.","additionalProperties":true},"example":{"reference":"11111111-1111-1111-1111-111111111111"}}}},"401":{"description":"No token, an expired token, or not allowed to do this","content":{"application/json":{"schema":{"type":"object","properties":{"error_reference":{"type":"string","description":"Quote this when reporting a failure."},"error_type":{"type":"string"},"errors":{"type":"object","properties":{},"description":"Field name, or `message`, to a list of messages.","additionalProperties":true},"status_code":{"type":"integer"},"timestamp":{"type":"string"}},"required":["error_reference","error_type","errors","status_code","timestamp"],"description":"Every error from this service, whatever the cause. Rendered by InfrastructureError.","additionalProperties":true},"example":{"error_reference":"b7825cd3-59dc-43a9-9e1f-964676d3cf72","error_type":"NOT_FOUND_ERROR","errors":{"message":["Product with reference 11111111-1111-1111-1111-111111111111 not found."]},"status_code":401,"timestamp":"2026-09-30T10:00:00.000000"}}}},"404":{"description":"No such record","content":{"application/json":{"schema":{"type":"object","properties":{"error_reference":{"type":"string","description":"Quote this when reporting a failure."},"error_type":{"type":"string"},"errors":{"type":"object","properties":{},"description":"Field name, or `message`, to a list of messages.","additionalProperties":true},"status_code":{"type":"integer"},"timestamp":{"type":"string"}},"required":["error_reference","error_type","errors","status_code","timestamp"],"description":"Every error from this service, whatever the cause. Rendered by InfrastructureError.","additionalProperties":true},"example":{"error_reference":"b7825cd3-59dc-43a9-9e1f-964676d3cf72","error_type":"NOT_FOUND_ERROR","errors":{"message":["Product with reference 11111111-1111-1111-1111-111111111111 not found."]},"status_code":404,"timestamp":"2026-09-30T10:00:00.000000"}}}},"500":{"description":"Unexpected failure. Quote `error_reference` when reporting it","content":{"application/json":{"schema":{"type":"object","properties":{"error_reference":{"type":"string","description":"Quote this when reporting a failure."},"error_type":{"type":"string"},"errors":{"type":"object","properties":{},"description":"Field name, or `message`, to a list of messages.","additionalProperties":true},"status_code":{"type":"integer"},"timestamp":{"type":"string"}},"required":["error_reference","error_type","errors","status_code","timestamp"],"description":"Every error from this service, whatever the cause. Rendered by InfrastructureError.","additionalProperties":true},"example":{"error_reference":"b7825cd3-59dc-43a9-9e1f-964676d3cf72","error_type":"NOT_FOUND_ERROR","errors":{"message":["Product with reference 11111111-1111-1111-1111-111111111111 not found."]},"status_code":500,"timestamp":"2026-09-30T10:00:00.000000"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}}},"components":{"schemas":{"HTTPValidationError":{"properties":{"detail":{"items":{"$ref":"#/components/schemas/ValidationError"},"type":"array","title":"Detail"}},"type":"object","title":"HTTPValidationError"},"ValidationError":{"properties":{"loc":{"items":{"anyOf":[{"type":"string"},{"type":"integer"}]},"type":"array","title":"Location"},"msg":{"type":"string","title":"Message"},"type":{"type":"string","title":"Error Type"}},"type":"object","required":["loc","msg","type"],"title":"ValidationError"}},"securitySchemes":{"HTTPBearer":{"type":"http","description":"The access token from sign-in.","scheme":"bearer"}}}}