Skip to content

Commit b99b574

Browse files
feat(llmobs): allow versioned dataset pulling (#14921)
## Description this PR allows users to optionally pull a specific version of a dataset by specifying the version in the `pull_dataset` call ## Testing with the following script to first pull the latest version, then a specified version, and a non existent version, we get the following output: ``` import os import math from dotenv import load_dotenv # Load environment variables from the .env file. load_dotenv(override=True) from ddtrace.llmobs import LLMObs LLMObs.enable(api_key=os.getenv("DD_API_KEY"), app_key=os.getenv("DD_APPLICATION_KEY"), project_name="Onboarding", ml_app="Onboarding-ML-App") import ddtrace print(ddtrace.get_version()) dataset = LLMObs.pull_dataset("1-then-big-gh-09021124", project_name="default-project") print("LATEST VERSION") print(dataset.as_dataframe()) print(f"version: {dataset.version}") print(f"current version: {dataset.latest_version}") print(dataset.url) print("\nOLDER BELOW") dataset1 = LLMObs.pull_dataset("1-then-big-gh-09021124", project_name="default-project", version=5) print(dataset1.as_dataframe()) print(f"version: {dataset1.version}") print(f"current version: {dataset1.latest_version}") print(dataset1.url) print("\nNON EXISTENT VERSION") dataset2 = LLMObs.pull_dataset("1-then-big-gh-09021124", project_name="default-project", version=500) print(dataset2.as_dataframe()) print(f"version: {dataset2.version}") print(f"current version: {dataset2.latest_version}") print(dataset2.url) ``` output: ``` 3.18.0.dev5+g960d810f4 LATEST VERSION unexpected metadata format <class 'str'> expected_output input_data 0 bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb... aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa... 1 bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb... aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa... 2 bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb... aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa... 3 bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb... aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa... 4 bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb... aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa... ... ... ... 45000 bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb... aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa... 45001 bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb... aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa... 45002 bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb... aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa... 45003 bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb... aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa... 45004 1 first [45005 rows x 2 columns] version: 6 current version: 6 https://app.datadoghq.com/llm/datasets/a68e7967-9d2b-4171-a887-f55ba4dbfe5e OLDER BELOW expected_output input_data 0 bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb... aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa... 1 bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb... aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa... 2 bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb... aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa... 3 bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb... aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa... 4 bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb... aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa... ... ... ... 34999 bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb... aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa... 35000 bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb... aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa... 35001 bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb... aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa... 35002 bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb... aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa... 35003 1 first [35004 rows x 2 columns] version: 5 current version: 6 https://app.datadoghq.com/llm/datasets/a68e7967-9d2b-4171-a887-f55ba4dbfe5e NON EXISTENT VERSION Traceback (most recent call last): File "/Users/gary.huang/go/src/github.com/DataDog/llm-observability/preview/experiments/notebooks/test-big-pull.py", line 35, in <module> dataset2 = LLMObs.pull_dataset("1-then-big-gh-09021124", project_name="default-project", version=500) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/Users/gary.huang/go/src/github.com/DataDog/dd-trace-py/ddtrace/llmobs/_llmobs.py", line 675, in pull_dataset ds = cls._instance._dne_client.dataset_get_with_records( ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/Users/gary.huang/go/src/github.com/DataDog/dd-trace-py/ddtrace/llmobs/_writer.py", line 509, in dataset_get_with_records raise ValueError( ValueError: Failed to pull dataset records for 1-then-big-gh-09021124, page=0: 400 {'errors': [{'title': 'Generic Error', 'detail': 'invalid version: version is greater than the current version or negative'}]} ``` the number of entries match the different versions of the dataset: https://dddev.datadoghq.com/llm/datasets/a68e7967-9d2b-4171-a887-f55ba4dbfe5e ## Risks <!-- Note any risks associated with this change, or "None" if no risks --> ## Additional Notes <!-- Any other information that would be helpful for reviewers --> --------- Co-authored-by: Sam Brenner <[email protected]>
1 parent e1c38c1 commit b99b574

File tree

17 files changed

+754
-51
lines changed

17 files changed

+754
-51
lines changed

ddtrace/llmobs/_experiment.py

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -107,6 +107,7 @@ class Dataset:
107107
_id: str
108108
_records: List[DatasetRecord]
109109
_version: int
110+
_latest_version: int
110111
_dne_client: "LLMObsExperimentsClient"
111112
_new_records_by_record_id: Dict[str, DatasetRecordRaw]
112113
_updated_record_ids_to_new_fields: Dict[str, UpdatableDatasetRecord]
@@ -121,13 +122,15 @@ def __init__(
121122
dataset_id: str,
122123
records: List[DatasetRecord],
123124
description: str,
125+
latest_version: int,
124126
version: int,
125127
_dne_client: "LLMObsExperimentsClient",
126128
) -> None:
127129
self.name = name
128130
self.project = project
129131
self.description = description
130132
self._id = dataset_id
133+
self._latest_version = latest_version
131134
self._version = version
132135
self._dne_client = _dne_client
133136
self._records = records
@@ -168,7 +171,10 @@ def push(self) -> None:
168171
record["record_id"] = record_id # type: ignore
169172

170173
# FIXME: we don't get version numbers in responses to deletion requests
171-
self._version = new_version if new_version != -1 else self._version + 1
174+
self._latest_version = new_version if new_version != -1 else self._latest_version + 1
175+
# no matter what the version was before the push, pushing will result in the dataset being on the current
176+
# version tracked by the backend
177+
self._version = self._latest_version
172178
self._new_records_by_record_id = {}
173179
self._deleted_record_ids = []
174180
self._updated_record_ids_to_new_fields = {}
@@ -225,6 +231,14 @@ def url(self) -> str:
225231
# FIXME: will not work for subdomain orgs
226232
return f"{_get_base_url()}/llm/datasets/{self._id}"
227233

234+
@property
235+
def latest_version(self) -> int:
236+
return self._latest_version
237+
238+
@property
239+
def version(self) -> int:
240+
return self._version
241+
228242
def _estimate_delta_size(self) -> int:
229243
"""rough estimate (in bytes) of the size of the next batch update call if it happens"""
230244
size = len(safe_json(self._new_records_by_record_id)) + len(safe_json(self._updated_record_ids_to_new_fields))
@@ -434,6 +448,7 @@ def _run_task(self, jobs: int, raise_errors: bool = False, sample_size: Optional
434448
dataset_id=self._dataset._id,
435449
records=subset_records,
436450
description=self._dataset.description,
451+
latest_version=self._dataset._latest_version,
437452
version=self._dataset._version,
438453
_dne_client=self._dataset._dne_client,
439454
)

ddtrace/llmobs/_llmobs.py

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -669,8 +669,12 @@ def _on_asyncio_execute_task(self, task_data: Dict[str, Any]) -> None:
669669
self._llmobs_context_provider.activate(llmobs_ctx)
670670

671671
@classmethod
672-
def pull_dataset(cls, dataset_name: str, project_name: Optional[str] = None) -> Dataset:
673-
ds = cls._instance._dne_client.dataset_get_with_records(dataset_name, (project_name or cls._project_name))
672+
def pull_dataset(
673+
cls, dataset_name: str, project_name: Optional[str] = None, version: Optional[int] = None
674+
) -> Dataset:
675+
ds = cls._instance._dne_client.dataset_get_with_records(
676+
dataset_name, (project_name or cls._project_name), version
677+
)
674678
return ds
675679

676680
@classmethod

ddtrace/llmobs/_writer.py

Lines changed: 35 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
from typing import TypedDict
1212
from typing import Union
1313
from typing import cast
14+
import urllib
1415
from urllib.parse import quote
1516
from urllib.parse import urlparse
1617

@@ -400,7 +401,16 @@ def dataset_create(
400401
if dataset_id is None or dataset_id == "":
401402
raise ValueError(f"unexpected dataset state, invalid ID (is None: {dataset_id is None})")
402403
curr_version = response_data["data"]["attributes"]["current_version"]
403-
return Dataset(dataset_name, project, dataset_id, [], description, curr_version, _dne_client=self)
404+
return Dataset(
405+
name=dataset_name,
406+
project=project,
407+
dataset_id=dataset_id,
408+
records=[],
409+
description=description,
410+
latest_version=curr_version,
411+
version=curr_version,
412+
_dne_client=self,
413+
)
404414

405415
@staticmethod
406416
def _get_record_json(record: Union[UpdatableDatasetRecord, DatasetRecordRaw], is_update: bool) -> JSONType:
@@ -458,10 +468,14 @@ def dataset_batch_update(
458468
new_record_ids: List[str] = [r["id"] for r in data] if data else []
459469
return new_version, new_record_ids
460470

461-
def dataset_get_with_records(self, dataset_name: str, project_name: Optional[str] = None) -> Dataset:
471+
def dataset_get_with_records(
472+
self, dataset_name: str, project_name: Optional[str] = None, version: Optional[int] = None
473+
) -> Dataset:
462474
project = self.project_create_or_get(project_name)
463475
project_id = project.get("_id")
464-
logger.debug("getting records with project ID %s for %s", project_id, project_name)
476+
logger.debug(
477+
"getting records with project ID %s for %s, version: %s", project_id, project_name, str(version) or "latest"
478+
)
465479

466480
path = f"/api/unstable/llm-obs/v1/{project_id}/datasets?filter[name]={quote(dataset_name)}"
467481
resp = self.request("GET", path)
@@ -480,11 +494,17 @@ def dataset_get_with_records(self, dataset_name: str, project_name: Optional[str
480494
dataset_id = data[0]["id"]
481495

482496
list_base_path = f"/api/unstable/llm-obs/v1/datasets/{dataset_id}/records"
497+
483498
has_next_page = True
484499
class_records: List[DatasetRecord] = []
485-
list_path = list_base_path
486500
page_num = 0
501+
url_options = {}
487502
while has_next_page:
503+
if version:
504+
url_options["filter[version]"] = version
505+
506+
list_path = f"{list_base_path}?{urllib.parse.urlencode(url_options, safe='[]')}"
507+
logger.debug("list records page %d, request path=%s", page_num, list_path)
488508
resp = self.request("GET", list_path, timeout=self.LIST_RECORDS_TIMEOUT)
489509
if resp.status != 200:
490510
raise ValueError(
@@ -504,14 +524,22 @@ def dataset_get_with_records(self, dataset_name: str, project_name: Optional[str
504524
}
505525
)
506526
next_cursor = records_data.get("meta", {}).get("after")
527+
528+
url_options = {}
507529
has_next_page = False
508530
if next_cursor:
509531
has_next_page = True
510-
list_path = f"{list_base_path}?page[cursor]={next_cursor}"
511-
logger.debug("next list records request path %s", list_path)
532+
url_options["page[cursor]"] = next_cursor
512533
page_num += 1
513534
return Dataset(
514-
dataset_name, project, dataset_id, class_records, dataset_description, curr_version, _dne_client=self
535+
name=dataset_name,
536+
project=project,
537+
dataset_id=dataset_id,
538+
records=class_records,
539+
description=dataset_description,
540+
latest_version=curr_version,
541+
version=version or curr_version,
542+
_dne_client=self,
515543
)
516544

517545
def dataset_bulk_upload(self, dataset_id: str, records: List[DatasetRecord]):
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
---
2+
features:
3+
- |
4+
LLM Observability: Previous dataset versions can be optionally pulled by passing the ``version``
5+
argument to ``LLMObs.pull_dataset``
6+
- |
7+
LLM Observability: Datasets have new properties ``version`` and ``latest_version`` to provide information on the
8+
version of the dataset that is being worked with and the latest global version of the dataset, respectively
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
interactions:
2+
- request:
3+
body: '{"data": {"type": "datasets", "id": "0bb93ae7-43c4-48ff-91e4-d2817fee85fe",
4+
"attributes": {"insert_records": [{"input": {"prompt": "What is the capital
5+
of France?"}, "expected_output": {"answer": "Paris"}, "metadata": null}], "update_records":
6+
[], "delete_records": []}}}'
7+
headers:
8+
Accept:
9+
- '*/*'
10+
? !!python/object/apply:multidict._multidict.istr
11+
- Accept-Encoding
12+
: - identity
13+
Connection:
14+
- keep-alive
15+
Content-Length:
16+
- '271'
17+
? !!python/object/apply:multidict._multidict.istr
18+
- Content-Type
19+
: - application/json
20+
User-Agent:
21+
- python-requests/2.32.3
22+
method: POST
23+
uri: https://api.datadoghq.com/api/unstable/llm-obs/v1/datasets/0bb93ae7-43c4-48ff-91e4-d2817fee85fe/batch_update
24+
response:
25+
body:
26+
string: '{"data":[{"id":"eaadecb4-836e-49b3-8390-212b3fffb60b","type":"datasets","attributes":{"author":{"id":"de473b30-eb9f-11e9-a77a-c7405862b8bd"},"created_at":"2025-10-21T18:26:24.929416376Z","dataset_id":"0bb93ae7-43c4-48ff-91e4-d2817fee85fe","expected_output":{"answer":"Paris"},"input":{"prompt":"What
27+
is the capital of France?"},"updated_at":"2025-10-21T18:26:24.929416376Z","version":1}}]}'
28+
headers:
29+
content-length:
30+
- '389'
31+
content-security-policy:
32+
- frame-ancestors 'self'; report-uri https://logs.browser-intake-datadoghq.com/api/v2/logs?dd-api-key=pube4f163c23bbf91c16b8f57f56af9fc58&dd-evp-origin=content-security-policy&ddsource=csp-report&ddtags=site%3Adatadoghq.com
33+
content-type:
34+
- application/vnd.api+json
35+
date:
36+
- Tue, 21 Oct 2025 18:26:24 GMT
37+
strict-transport-security:
38+
- max-age=31536000; includeSubDomains; preload
39+
vary:
40+
- Accept-Encoding
41+
x-content-type-options:
42+
- nosniff
43+
x-frame-options:
44+
- SAMEORIGIN
45+
status:
46+
code: 200
47+
message: OK
48+
version: 1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
interactions:
2+
- request:
3+
body: null
4+
headers:
5+
Accept:
6+
- '*/*'
7+
? !!python/object/apply:multidict._multidict.istr
8+
- Accept-Encoding
9+
: - identity
10+
Connection:
11+
- keep-alive
12+
? !!python/object/apply:multidict._multidict.istr
13+
- Content-Length
14+
: - '0'
15+
? !!python/object/apply:multidict._multidict.istr
16+
- Content-Type
17+
: - application/json
18+
User-Agent:
19+
- python-requests/2.32.3
20+
method: GET
21+
uri: https://api.datadoghq.com/api/unstable/llm-obs/v1/datasets/0bb93ae7-43c4-48ff-91e4-d2817fee85fe/records?filter%5Bversion%5D=420
22+
response:
23+
body:
24+
string: '{"errors":[{"title":"Generic Error","detail":"invalid version: version
25+
is greater than the current version or negative"}]}'
26+
headers:
27+
content-length:
28+
- '122'
29+
content-security-policy:
30+
- frame-ancestors 'self'; report-uri https://logs.browser-intake-datadoghq.com/api/v2/logs?dd-api-key=pube4f163c23bbf91c16b8f57f56af9fc58&dd-evp-origin=content-security-policy&ddsource=csp-report&ddtags=site%3Adatadoghq.com
31+
content-type:
32+
- application/vnd.api+json
33+
date:
34+
- Tue, 21 Oct 2025 18:26:27 GMT
35+
strict-transport-security:
36+
- max-age=31536000; includeSubDomains; preload
37+
vary:
38+
- Accept-Encoding
39+
x-content-type-options:
40+
- nosniff
41+
x-frame-options:
42+
- SAMEORIGIN
43+
status:
44+
code: 400
45+
message: Bad Request
46+
version: 1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
interactions:
2+
- request:
3+
body: '{"data": {"type": "datasets", "id": "4607e918-094d-4aa9-8b7a-50fa63a95b56",
4+
"attributes": {"insert_records": [{"input": {"prompt": "What is the capital
5+
of France?"}, "expected_output": {"answer": "Paris"}, "metadata": null}], "update_records":
6+
[], "delete_records": []}}}'
7+
headers:
8+
Accept:
9+
- '*/*'
10+
? !!python/object/apply:multidict._multidict.istr
11+
- Accept-Encoding
12+
: - identity
13+
Connection:
14+
- keep-alive
15+
Content-Length:
16+
- '271'
17+
? !!python/object/apply:multidict._multidict.istr
18+
- Content-Type
19+
: - application/json
20+
User-Agent:
21+
- python-requests/2.32.3
22+
method: POST
23+
uri: https://api.datadoghq.com/api/unstable/llm-obs/v1/datasets/4607e918-094d-4aa9-8b7a-50fa63a95b56/batch_update
24+
response:
25+
body:
26+
string: '{"data":[{"id":"93328f7a-bfd2-4672-8b94-76b0698cd754","type":"datasets","attributes":{"author":{"id":"de473b30-eb9f-11e9-a77a-c7405862b8bd"},"created_at":"2025-10-21T18:25:23.855004356Z","dataset_id":"4607e918-094d-4aa9-8b7a-50fa63a95b56","expected_output":{"answer":"Paris"},"input":{"prompt":"What
27+
is the capital of France?"},"updated_at":"2025-10-21T18:25:23.855004356Z","version":1}}]}'
28+
headers:
29+
content-length:
30+
- '389'
31+
content-security-policy:
32+
- frame-ancestors 'self'; report-uri https://logs.browser-intake-datadoghq.com/api/v2/logs?dd-api-key=pube4f163c23bbf91c16b8f57f56af9fc58&dd-evp-origin=content-security-policy&ddsource=csp-report&ddtags=site%3Adatadoghq.com
33+
content-type:
34+
- application/vnd.api+json
35+
date:
36+
- Tue, 21 Oct 2025 18:25:23 GMT
37+
strict-transport-security:
38+
- max-age=31536000; includeSubDomains; preload
39+
vary:
40+
- Accept-Encoding
41+
x-content-type-options:
42+
- nosniff
43+
x-frame-options:
44+
- SAMEORIGIN
45+
status:
46+
code: 200
47+
message: OK
48+
version: 1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
interactions:
2+
- request:
3+
body: '{"data": {"type": "datasets", "id": "4607e918-094d-4aa9-8b7a-50fa63a95b56",
4+
"attributes": {"insert_records": [{"input": {"prompt": "What is the capital
5+
of China?"}, "expected_output": {"answer": "Beijing"}, "metadata": null}], "update_records":
6+
[], "delete_records": []}}}'
7+
headers:
8+
Accept:
9+
- '*/*'
10+
? !!python/object/apply:multidict._multidict.istr
11+
- Accept-Encoding
12+
: - identity
13+
Connection:
14+
- keep-alive
15+
Content-Length:
16+
- '272'
17+
? !!python/object/apply:multidict._multidict.istr
18+
- Content-Type
19+
: - application/json
20+
User-Agent:
21+
- python-requests/2.32.3
22+
method: POST
23+
uri: https://api.datadoghq.com/api/unstable/llm-obs/v1/datasets/4607e918-094d-4aa9-8b7a-50fa63a95b56/batch_update
24+
response:
25+
body:
26+
string: '{"data":[{"id":"5bbd89ec-4eba-4f41-bd47-2a23a005a20a","type":"datasets","attributes":{"author":{"id":"de473b30-eb9f-11e9-a77a-c7405862b8bd"},"created_at":"2025-10-21T18:25:26.024986597Z","dataset_id":"4607e918-094d-4aa9-8b7a-50fa63a95b56","expected_output":{"answer":"Beijing"},"input":{"prompt":"What
27+
is the capital of China?"},"updated_at":"2025-10-21T18:25:26.024986597Z","version":2}}]}'
28+
headers:
29+
content-length:
30+
- '390'
31+
content-security-policy:
32+
- frame-ancestors 'self'; report-uri https://logs.browser-intake-datadoghq.com/api/v2/logs?dd-api-key=pube4f163c23bbf91c16b8f57f56af9fc58&dd-evp-origin=content-security-policy&ddsource=csp-report&ddtags=site%3Adatadoghq.com
33+
content-type:
34+
- application/vnd.api+json
35+
date:
36+
- Tue, 21 Oct 2025 18:25:26 GMT
37+
strict-transport-security:
38+
- max-age=31536000; includeSubDomains; preload
39+
vary:
40+
- Accept-Encoding
41+
x-content-type-options:
42+
- nosniff
43+
x-frame-options:
44+
- SAMEORIGIN
45+
status:
46+
code: 200
47+
message: OK
48+
version: 1

0 commit comments

Comments
 (0)