diff --git a/.release-please-manifest.json b/.release-please-manifest.json
index 1b77f50..6538ca9 100644
--- a/.release-please-manifest.json
+++ b/.release-please-manifest.json
@@ -1,3 +1,3 @@
{
- ".": "0.7.0"
+ ".": "0.8.0"
}
\ No newline at end of file
diff --git a/.stats.yml b/.stats.yml
index c73ca81..26bad1a 100644
--- a/.stats.yml
+++ b/.stats.yml
@@ -1,4 +1,4 @@
-configured_endpoints: 12
-openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/azure/partnermax-38113ab2a8a24debc22a07ed35d7f367b7faf132cdf0db5228c1d4c68d7f41f5.yml
-openapi_spec_hash: 420922592a8c90a9fe4b811b9864ddc1
-config_hash: bf921bb2f4db038ffed15141c74cd018
+configured_endpoints: 18
+openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/azure/partnermax-2efc95334578123c6faee89c66575c440aec573d479dd6b6e0ce0d6477472e23.yml
+openapi_spec_hash: c97c1474daf815592ca1a3a9fd29e482
+config_hash: ef989e9f9b2271740f1b742f1ab804af
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 29a5e6b..8af65ae 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,14 @@
# Changelog
+## 0.8.0 (2026-05-18)
+
+Full Changelog: [v0.7.0...v0.8.0](https://github.com/DealerMax-app/partnermax-python/compare/v0.7.0...v0.8.0)
+
+### Features
+
+* **vehicles:** used-vehicle CRUD + bulk insert for v1.1.0 ([c817349](https://github.com/DealerMax-app/partnermax-python/commit/c817349ace294552a56e191f975a444a92b82548))
+* **vehicles:** used-vehicle CRUD + bulk insert for v1.1.0 ([611cb75](https://github.com/DealerMax-app/partnermax-python/commit/611cb752d4d71443a5f1c6c22d24394a87ffa295))
+
## 0.7.0 (2026-05-18)
Full Changelog: [v0.6.0...v0.7.0](https://github.com/DealerMax-app/partnermax-python/compare/v0.6.0...v0.7.0)
diff --git a/README.md b/README.md
index 0bb54d9..13465f4 100644
--- a/README.md
+++ b/README.md
@@ -35,13 +35,17 @@ client = Partnermax(
)
dealer_detail = client.dealers.create(
+ address="xx",
business_name="Rossi Automobili S.R.L.",
+ city="xx",
contact_email="info@rossi-auto.it",
+ contact_phone="xxxxx",
postal_code="20121",
primary_domain="rossi-auto.it",
province_code="MI",
vat_number="IT01234567890",
)
+print(dealer_detail.dealer_id)
```
While you can provide an `api_key` keyword argument,
@@ -67,13 +71,17 @@ client = AsyncPartnermax(
async def main() -> None:
dealer_detail = await client.dealers.create(
+ address="xx",
business_name="Rossi Automobili S.R.L.",
+ city="xx",
contact_email="info@rossi-auto.it",
+ contact_phone="xxxxx",
postal_code="20121",
primary_domain="rossi-auto.it",
province_code="MI",
vat_number="IT01234567890",
)
+ print(dealer_detail.dealer_id)
asyncio.run(main())
@@ -107,13 +115,17 @@ async def main() -> None:
http_client=DefaultAioHttpClient(),
) as client:
dealer_detail = await client.dealers.create(
+ address="xx",
business_name="Rossi Automobili S.R.L.",
+ city="xx",
contact_email="info@rossi-auto.it",
+ contact_phone="xxxxx",
postal_code="20121",
primary_domain="rossi-auto.it",
province_code="MI",
vat_number="IT01234567890",
)
+ print(dealer_detail.dealer_id)
asyncio.run(main())
@@ -139,11 +151,11 @@ client = Partnermax()
nlt_settings = client.dealers.nlt_settings.update(
dealer_id="dealer_id",
- agency_markup_percent=3.5,
+ agency_markup_percent=0,
down_payment_tiers={
"high": {
"fixed_eur": 0,
- "percent_of_list": 25,
+ "percent_of_list": 0,
},
"low": {
"fixed_eur": 0,
@@ -151,10 +163,9 @@ nlt_settings = client.dealers.nlt_settings.update(
},
"medium": {
"fixed_eur": 0,
- "percent_of_list": 12.5,
+ "percent_of_list": 0,
},
},
- image_mode="branded",
)
print(nlt_settings.down_payment_tiers)
```
diff --git a/api.md b/api.md
index 860568b..bca3acb 100644
--- a/api.md
+++ b/api.md
@@ -55,3 +55,26 @@ Methods:
- client.dealers.nlt.offers.retrieve(offer_id, \*, dealer_id) -> OfferRetrieveResponse
- client.dealers.nlt.offers.list(dealer_id, \*\*params) -> OfferListResponse
+
+## Vehicles
+
+Types:
+
+```python
+from partnermax.types.dealers import (
+ BulkCreateVehiclesResponse,
+ BulkRowOutcome,
+ VehicleDetail,
+ VehicleList,
+ VehicleSummary,
+)
+```
+
+Methods:
+
+- client.dealers.vehicles.create(dealer_id, \*\*params) -> VehicleDetail
+- client.dealers.vehicles.retrieve(vehicle_id, \*, dealer_id, \*\*params) -> VehicleDetail
+- client.dealers.vehicles.update(vehicle_id, \*, dealer_id, \*\*params) -> VehicleDetail
+- client.dealers.vehicles.list(dealer_id, \*\*params) -> VehicleList
+- client.dealers.vehicles.delete(vehicle_id, \*, dealer_id) -> None
+- client.dealers.vehicles.bulk(dealer_id, \*\*params) -> BulkCreateVehiclesResponse
diff --git a/pyproject.toml b/pyproject.toml
index 5cb85c9..0f907fa 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -1,6 +1,6 @@
[project]
name = "partnermax"
-version = "0.7.0"
+version = "0.8.0"
description = "The official Python library for the partnermax API"
dynamic = ["readme"]
license = "Apache-2.0"
diff --git a/src/partnermax/_version.py b/src/partnermax/_version.py
index 7663be0..06afba4 100644
--- a/src/partnermax/_version.py
+++ b/src/partnermax/_version.py
@@ -1,4 +1,4 @@
# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
__title__ = "partnermax"
-__version__ = "0.7.0" # x-release-please-version
+__version__ = "0.8.0" # x-release-please-version
diff --git a/src/partnermax/resources/dealers/__init__.py b/src/partnermax/resources/dealers/__init__.py
index 2537f24..edd3e7c 100644
--- a/src/partnermax/resources/dealers/__init__.py
+++ b/src/partnermax/resources/dealers/__init__.py
@@ -16,6 +16,14 @@
DealersResourceWithStreamingResponse,
AsyncDealersResourceWithStreamingResponse,
)
+from .vehicles import (
+ VehiclesResource,
+ AsyncVehiclesResource,
+ VehiclesResourceWithRawResponse,
+ AsyncVehiclesResourceWithRawResponse,
+ VehiclesResourceWithStreamingResponse,
+ AsyncVehiclesResourceWithStreamingResponse,
+)
from .nlt_settings import (
NltSettingsResource,
AsyncNltSettingsResource,
@@ -38,6 +46,12 @@
"AsyncNltResourceWithRawResponse",
"NltResourceWithStreamingResponse",
"AsyncNltResourceWithStreamingResponse",
+ "VehiclesResource",
+ "AsyncVehiclesResource",
+ "VehiclesResourceWithRawResponse",
+ "AsyncVehiclesResourceWithRawResponse",
+ "VehiclesResourceWithStreamingResponse",
+ "AsyncVehiclesResourceWithStreamingResponse",
"DealersResource",
"AsyncDealersResource",
"DealersResourceWithRawResponse",
diff --git a/src/partnermax/resources/dealers/dealers.py b/src/partnermax/resources/dealers/dealers.py
index 6b38d94..bb4b7da 100644
--- a/src/partnermax/resources/dealers/dealers.py
+++ b/src/partnermax/resources/dealers/dealers.py
@@ -2,7 +2,7 @@
from __future__ import annotations
-from typing import Dict
+from typing import Dict, Optional
from typing_extensions import Literal
import httpx
@@ -18,6 +18,14 @@
)
from ..._types import Body, Omit, Query, Headers, NoneType, NotGiven, omit, not_given
from ..._utils import path_template, maybe_transform, strip_not_given, async_maybe_transform
+from .vehicles import (
+ VehiclesResource,
+ AsyncVehiclesResource,
+ VehiclesResourceWithRawResponse,
+ AsyncVehiclesResourceWithRawResponse,
+ VehiclesResourceWithStreamingResponse,
+ AsyncVehiclesResourceWithStreamingResponse,
+)
from ..._compat import cached_property
from ..._resource import SyncAPIResource, AsyncAPIResource
from ..._response import (
@@ -46,13 +54,20 @@ class DealersResource(SyncAPIResource):
@cached_property
def nlt_settings(self) -> NltSettingsResource:
- """Per-dealer NLT economics: agency markup percent and three down-payment tiers."""
return NltSettingsResource(self._client)
@cached_property
def nlt(self) -> NltResource:
return NltResource(self._client)
+ @cached_property
+ def vehicles(self) -> VehiclesResource:
+ """Used-vehicle stock management for partner-owned dealers.
+
+ The partner uploads each used vehicle by its canonical Motornet UNI code; DealerMAX joins the partner-provided pricing and stock metadata with the catalog master so the resulting listing is immediately indexed by the AI surfaces (MCP server, ChatGPT Custom GPT, NLWeb /ask, and the SEO/JSON-LD layer).
+ """
+ return VehiclesResource(self._client)
+
@cached_property
def with_raw_response(self) -> DealersResourceWithRawResponse:
"""
@@ -75,14 +90,16 @@ def with_streaming_response(self) -> DealersResourceWithStreamingResponse:
def create(
self,
*,
+ address: str,
business_name: str,
+ city: str,
contact_email: str,
+ contact_phone: str,
postal_code: str,
primary_domain: str,
province_code: str,
vat_number: str,
activate: bool | Omit = omit,
- contact_phone: str | Omit = omit,
metadata: Dict[str, str] | Omit = omit,
idempotency_key: str | Omit = omit,
# Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
@@ -92,32 +109,10 @@ def create(
extra_body: Body | None = None,
timeout: float | httpx.Timeout | None | NotGiven = not_given,
) -> DealerDetail:
- """Creates a new dealer as a child of the calling partner account.
-
- The dealer is
- indexed in the cross-network AI-citation surfaces (MCP, Custom GPT, NLWeb,
- llms.txt) within five minutes. The partner is responsible for ensuring the
- business has consented to this provisioning and that the data provided is
- accurate.
-
- Idempotent on `Idempotency-Key` for 24 hours.
+ """
+ Provision a new dealer as child of the calling partner.
Args:
- postal_code: Italian 5-digit postal code.
-
- primary_domain: Root domain of the dealer's public website.
-
- province_code: Italian two-letter province code, e.g., `MI`, `RM`, `TO`.
-
- vat_number: Italian VAT number, 11 digits prefixed with `IT`.
-
- activate: If false, dealer is created in inactive state and does not appear in AI surfaces
- until activated.
-
- contact_phone: E.164 format recommended.
-
- metadata: Free-form partner-supplied key-value pairs, max 16 keys, values max 500 chars.
-
extra_headers: Send extra headers
extra_query: Add additional query parameters to the request
@@ -131,14 +126,16 @@ def create(
"/v1/dealers",
body=maybe_transform(
{
+ "address": address,
"business_name": business_name,
+ "city": city,
"contact_email": contact_email,
+ "contact_phone": contact_phone,
"postal_code": postal_code,
"primary_domain": primary_domain,
"province_code": province_code,
"vat_number": vat_number,
"activate": activate,
- "contact_phone": contact_phone,
"metadata": metadata,
},
dealer_create_params.DealerCreateParams,
@@ -160,8 +157,9 @@ def retrieve(
extra_body: Body | None = None,
timeout: float | httpx.Timeout | None | NotGiven = not_given,
) -> DealerDetail:
- """
- Get a dealer's full detail
+ """Fetch a dealer's full detail.
+
+ ACL-protected.
Args:
extra_headers: Send extra headers
@@ -186,13 +184,15 @@ def update(
self,
dealer_id: str,
*,
- business_name: str | Omit = omit,
- contact_email: str | Omit = omit,
- contact_phone: str | Omit = omit,
- metadata: Dict[str, str] | Omit = omit,
- postal_code: str | Omit = omit,
- province_code: str | Omit = omit,
- status: Literal["active", "inactive"] | Omit = omit,
+ address: Optional[str] | Omit = omit,
+ business_name: Optional[str] | Omit = omit,
+ city: Optional[str] | Omit = omit,
+ contact_email: Optional[str] | Omit = omit,
+ contact_phone: Optional[str] | Omit = omit,
+ metadata: Optional[Dict[str, str]] | Omit = omit,
+ postal_code: Optional[str] | Omit = omit,
+ province_code: Optional[str] | Omit = omit,
+ status: Optional[Literal["active", "inactive"]] | Omit = omit,
idempotency_key: str | Omit = omit,
# Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
# The extra values given here take precedence over values defined on the client or passed to this method.
@@ -201,16 +201,11 @@ def update(
extra_body: Body | None = None,
timeout: float | httpx.Timeout | None | NotGiven = not_given,
) -> DealerDetail:
- """Partial update of dealer fields.
+ """Update or toggle status.
- Only provided fields are modified. Setting
- `status: "inactive"` removes the dealer from the cross-network AI-citation
- surfaces within five minutes. Reactivation: send `status: "active"`.
+ Inactive dealers drop from AI surfaces within 5 min.
Args:
- status: Toggle activation. Inactive dealers are removed from AI surfaces within 5
- minutes.
-
extra_headers: Send extra headers
extra_query: Add additional query parameters to the request
@@ -226,7 +221,9 @@ def update(
path_template("/v1/dealers/{dealer_id}", dealer_id=dealer_id),
body=maybe_transform(
{
+ "address": address,
"business_name": business_name,
+ "city": city,
"contact_email": contact_email,
"contact_phone": contact_phone,
"metadata": metadata,
@@ -245,7 +242,7 @@ def update(
def list(
self,
*,
- cursor: str | Omit = omit,
+ cursor: Optional[str] | Omit = omit,
limit: int | Omit = omit,
status: Literal["active", "inactive", "all"] | Omit = omit,
# Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
@@ -255,17 +252,11 @@ def list(
extra_body: Body | None = None,
timeout: float | httpx.Timeout | None | NotGiven = not_given,
) -> DealerListResponse:
- """
- Returns a cursor-paginated list of dealers belonging to the calling partner.
- Default ordering: most recently created first.
+ """List dealers owned by the calling partner.
- Args:
- cursor: Opaque pagination cursor from a previous response's `next_cursor`.
-
- limit: Maximum number of items to return.
-
- status: Filter by dealer status.
+ Cursor-paginated.
+ Args:
extra_headers: Send extra headers
extra_query: Add additional query parameters to the request
@@ -304,12 +295,9 @@ def delete(
extra_body: Body | None = None,
timeout: float | httpx.Timeout | None | NotGiven = not_given,
) -> None:
- """Marks the dealer as deleted while preserving the audit trail.
+ """Soft-delete.
- Unlike
- deactivation (`PATCH status=inactive`), a deleted dealer cannot be reactivated
- by the partner — re-creation requires DealerMAX support. Use deactivation for
- reversible suspensions.
+ Audit trail retained; reactivation requires DealerMAX support.
Args:
extra_headers: Send extra headers
@@ -337,13 +325,20 @@ class AsyncDealersResource(AsyncAPIResource):
@cached_property
def nlt_settings(self) -> AsyncNltSettingsResource:
- """Per-dealer NLT economics: agency markup percent and three down-payment tiers."""
return AsyncNltSettingsResource(self._client)
@cached_property
def nlt(self) -> AsyncNltResource:
return AsyncNltResource(self._client)
+ @cached_property
+ def vehicles(self) -> AsyncVehiclesResource:
+ """Used-vehicle stock management for partner-owned dealers.
+
+ The partner uploads each used vehicle by its canonical Motornet UNI code; DealerMAX joins the partner-provided pricing and stock metadata with the catalog master so the resulting listing is immediately indexed by the AI surfaces (MCP server, ChatGPT Custom GPT, NLWeb /ask, and the SEO/JSON-LD layer).
+ """
+ return AsyncVehiclesResource(self._client)
+
@cached_property
def with_raw_response(self) -> AsyncDealersResourceWithRawResponse:
"""
@@ -366,14 +361,16 @@ def with_streaming_response(self) -> AsyncDealersResourceWithStreamingResponse:
async def create(
self,
*,
+ address: str,
business_name: str,
+ city: str,
contact_email: str,
+ contact_phone: str,
postal_code: str,
primary_domain: str,
province_code: str,
vat_number: str,
activate: bool | Omit = omit,
- contact_phone: str | Omit = omit,
metadata: Dict[str, str] | Omit = omit,
idempotency_key: str | Omit = omit,
# Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
@@ -383,32 +380,10 @@ async def create(
extra_body: Body | None = None,
timeout: float | httpx.Timeout | None | NotGiven = not_given,
) -> DealerDetail:
- """Creates a new dealer as a child of the calling partner account.
-
- The dealer is
- indexed in the cross-network AI-citation surfaces (MCP, Custom GPT, NLWeb,
- llms.txt) within five minutes. The partner is responsible for ensuring the
- business has consented to this provisioning and that the data provided is
- accurate.
-
- Idempotent on `Idempotency-Key` for 24 hours.
+ """
+ Provision a new dealer as child of the calling partner.
Args:
- postal_code: Italian 5-digit postal code.
-
- primary_domain: Root domain of the dealer's public website.
-
- province_code: Italian two-letter province code, e.g., `MI`, `RM`, `TO`.
-
- vat_number: Italian VAT number, 11 digits prefixed with `IT`.
-
- activate: If false, dealer is created in inactive state and does not appear in AI surfaces
- until activated.
-
- contact_phone: E.164 format recommended.
-
- metadata: Free-form partner-supplied key-value pairs, max 16 keys, values max 500 chars.
-
extra_headers: Send extra headers
extra_query: Add additional query parameters to the request
@@ -422,14 +397,16 @@ async def create(
"/v1/dealers",
body=await async_maybe_transform(
{
+ "address": address,
"business_name": business_name,
+ "city": city,
"contact_email": contact_email,
+ "contact_phone": contact_phone,
"postal_code": postal_code,
"primary_domain": primary_domain,
"province_code": province_code,
"vat_number": vat_number,
"activate": activate,
- "contact_phone": contact_phone,
"metadata": metadata,
},
dealer_create_params.DealerCreateParams,
@@ -451,8 +428,9 @@ async def retrieve(
extra_body: Body | None = None,
timeout: float | httpx.Timeout | None | NotGiven = not_given,
) -> DealerDetail:
- """
- Get a dealer's full detail
+ """Fetch a dealer's full detail.
+
+ ACL-protected.
Args:
extra_headers: Send extra headers
@@ -477,13 +455,15 @@ async def update(
self,
dealer_id: str,
*,
- business_name: str | Omit = omit,
- contact_email: str | Omit = omit,
- contact_phone: str | Omit = omit,
- metadata: Dict[str, str] | Omit = omit,
- postal_code: str | Omit = omit,
- province_code: str | Omit = omit,
- status: Literal["active", "inactive"] | Omit = omit,
+ address: Optional[str] | Omit = omit,
+ business_name: Optional[str] | Omit = omit,
+ city: Optional[str] | Omit = omit,
+ contact_email: Optional[str] | Omit = omit,
+ contact_phone: Optional[str] | Omit = omit,
+ metadata: Optional[Dict[str, str]] | Omit = omit,
+ postal_code: Optional[str] | Omit = omit,
+ province_code: Optional[str] | Omit = omit,
+ status: Optional[Literal["active", "inactive"]] | Omit = omit,
idempotency_key: str | Omit = omit,
# Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
# The extra values given here take precedence over values defined on the client or passed to this method.
@@ -492,16 +472,11 @@ async def update(
extra_body: Body | None = None,
timeout: float | httpx.Timeout | None | NotGiven = not_given,
) -> DealerDetail:
- """Partial update of dealer fields.
+ """Update or toggle status.
- Only provided fields are modified. Setting
- `status: "inactive"` removes the dealer from the cross-network AI-citation
- surfaces within five minutes. Reactivation: send `status: "active"`.
+ Inactive dealers drop from AI surfaces within 5 min.
Args:
- status: Toggle activation. Inactive dealers are removed from AI surfaces within 5
- minutes.
-
extra_headers: Send extra headers
extra_query: Add additional query parameters to the request
@@ -517,7 +492,9 @@ async def update(
path_template("/v1/dealers/{dealer_id}", dealer_id=dealer_id),
body=await async_maybe_transform(
{
+ "address": address,
"business_name": business_name,
+ "city": city,
"contact_email": contact_email,
"contact_phone": contact_phone,
"metadata": metadata,
@@ -536,7 +513,7 @@ async def update(
async def list(
self,
*,
- cursor: str | Omit = omit,
+ cursor: Optional[str] | Omit = omit,
limit: int | Omit = omit,
status: Literal["active", "inactive", "all"] | Omit = omit,
# Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
@@ -546,17 +523,11 @@ async def list(
extra_body: Body | None = None,
timeout: float | httpx.Timeout | None | NotGiven = not_given,
) -> DealerListResponse:
- """
- Returns a cursor-paginated list of dealers belonging to the calling partner.
- Default ordering: most recently created first.
+ """List dealers owned by the calling partner.
- Args:
- cursor: Opaque pagination cursor from a previous response's `next_cursor`.
-
- limit: Maximum number of items to return.
-
- status: Filter by dealer status.
+ Cursor-paginated.
+ Args:
extra_headers: Send extra headers
extra_query: Add additional query parameters to the request
@@ -595,12 +566,9 @@ async def delete(
extra_body: Body | None = None,
timeout: float | httpx.Timeout | None | NotGiven = not_given,
) -> None:
- """Marks the dealer as deleted while preserving the audit trail.
+ """Soft-delete.
- Unlike
- deactivation (`PATCH status=inactive`), a deleted dealer cannot be reactivated
- by the partner — re-creation requires DealerMAX support. Use deactivation for
- reversible suspensions.
+ Audit trail retained; reactivation requires DealerMAX support.
Args:
extra_headers: Send extra headers
@@ -645,13 +613,20 @@ def __init__(self, dealers: DealersResource) -> None:
@cached_property
def nlt_settings(self) -> NltSettingsResourceWithRawResponse:
- """Per-dealer NLT economics: agency markup percent and three down-payment tiers."""
return NltSettingsResourceWithRawResponse(self._dealers.nlt_settings)
@cached_property
def nlt(self) -> NltResourceWithRawResponse:
return NltResourceWithRawResponse(self._dealers.nlt)
+ @cached_property
+ def vehicles(self) -> VehiclesResourceWithRawResponse:
+ """Used-vehicle stock management for partner-owned dealers.
+
+ The partner uploads each used vehicle by its canonical Motornet UNI code; DealerMAX joins the partner-provided pricing and stock metadata with the catalog master so the resulting listing is immediately indexed by the AI surfaces (MCP server, ChatGPT Custom GPT, NLWeb /ask, and the SEO/JSON-LD layer).
+ """
+ return VehiclesResourceWithRawResponse(self._dealers.vehicles)
+
class AsyncDealersResourceWithRawResponse:
def __init__(self, dealers: AsyncDealersResource) -> None:
@@ -675,13 +650,20 @@ def __init__(self, dealers: AsyncDealersResource) -> None:
@cached_property
def nlt_settings(self) -> AsyncNltSettingsResourceWithRawResponse:
- """Per-dealer NLT economics: agency markup percent and three down-payment tiers."""
return AsyncNltSettingsResourceWithRawResponse(self._dealers.nlt_settings)
@cached_property
def nlt(self) -> AsyncNltResourceWithRawResponse:
return AsyncNltResourceWithRawResponse(self._dealers.nlt)
+ @cached_property
+ def vehicles(self) -> AsyncVehiclesResourceWithRawResponse:
+ """Used-vehicle stock management for partner-owned dealers.
+
+ The partner uploads each used vehicle by its canonical Motornet UNI code; DealerMAX joins the partner-provided pricing and stock metadata with the catalog master so the resulting listing is immediately indexed by the AI surfaces (MCP server, ChatGPT Custom GPT, NLWeb /ask, and the SEO/JSON-LD layer).
+ """
+ return AsyncVehiclesResourceWithRawResponse(self._dealers.vehicles)
+
class DealersResourceWithStreamingResponse:
def __init__(self, dealers: DealersResource) -> None:
@@ -705,13 +687,20 @@ def __init__(self, dealers: DealersResource) -> None:
@cached_property
def nlt_settings(self) -> NltSettingsResourceWithStreamingResponse:
- """Per-dealer NLT economics: agency markup percent and three down-payment tiers."""
return NltSettingsResourceWithStreamingResponse(self._dealers.nlt_settings)
@cached_property
def nlt(self) -> NltResourceWithStreamingResponse:
return NltResourceWithStreamingResponse(self._dealers.nlt)
+ @cached_property
+ def vehicles(self) -> VehiclesResourceWithStreamingResponse:
+ """Used-vehicle stock management for partner-owned dealers.
+
+ The partner uploads each used vehicle by its canonical Motornet UNI code; DealerMAX joins the partner-provided pricing and stock metadata with the catalog master so the resulting listing is immediately indexed by the AI surfaces (MCP server, ChatGPT Custom GPT, NLWeb /ask, and the SEO/JSON-LD layer).
+ """
+ return VehiclesResourceWithStreamingResponse(self._dealers.vehicles)
+
class AsyncDealersResourceWithStreamingResponse:
def __init__(self, dealers: AsyncDealersResource) -> None:
@@ -735,9 +724,16 @@ def __init__(self, dealers: AsyncDealersResource) -> None:
@cached_property
def nlt_settings(self) -> AsyncNltSettingsResourceWithStreamingResponse:
- """Per-dealer NLT economics: agency markup percent and three down-payment tiers."""
return AsyncNltSettingsResourceWithStreamingResponse(self._dealers.nlt_settings)
@cached_property
def nlt(self) -> AsyncNltResourceWithStreamingResponse:
return AsyncNltResourceWithStreamingResponse(self._dealers.nlt)
+
+ @cached_property
+ def vehicles(self) -> AsyncVehiclesResourceWithStreamingResponse:
+ """Used-vehicle stock management for partner-owned dealers.
+
+ The partner uploads each used vehicle by its canonical Motornet UNI code; DealerMAX joins the partner-provided pricing and stock metadata with the catalog master so the resulting listing is immediately indexed by the AI surfaces (MCP server, ChatGPT Custom GPT, NLWeb /ask, and the SEO/JSON-LD layer).
+ """
+ return AsyncVehiclesResourceWithStreamingResponse(self._dealers.vehicles)
diff --git a/src/partnermax/resources/dealers/nlt/nlt.py b/src/partnermax/resources/dealers/nlt/nlt.py
index 31938f7..46dafea 100644
--- a/src/partnermax/resources/dealers/nlt/nlt.py
+++ b/src/partnermax/resources/dealers/nlt/nlt.py
@@ -19,9 +19,6 @@
class NltResource(SyncAPIResource):
@cached_property
def offers(self) -> OffersResource:
- """
- Dealer-aware reads of the shared NLT catalog — pricing reflects the dealer's markup and down-payment tiers.
- """
return OffersResource(self._client)
@cached_property
@@ -47,9 +44,6 @@ def with_streaming_response(self) -> NltResourceWithStreamingResponse:
class AsyncNltResource(AsyncAPIResource):
@cached_property
def offers(self) -> AsyncOffersResource:
- """
- Dealer-aware reads of the shared NLT catalog — pricing reflects the dealer's markup and down-payment tiers.
- """
return AsyncOffersResource(self._client)
@cached_property
@@ -78,9 +72,6 @@ def __init__(self, nlt: NltResource) -> None:
@cached_property
def offers(self) -> OffersResourceWithRawResponse:
- """
- Dealer-aware reads of the shared NLT catalog — pricing reflects the dealer's markup and down-payment tiers.
- """
return OffersResourceWithRawResponse(self._nlt.offers)
@@ -90,9 +81,6 @@ def __init__(self, nlt: AsyncNltResource) -> None:
@cached_property
def offers(self) -> AsyncOffersResourceWithRawResponse:
- """
- Dealer-aware reads of the shared NLT catalog — pricing reflects the dealer's markup and down-payment tiers.
- """
return AsyncOffersResourceWithRawResponse(self._nlt.offers)
@@ -102,9 +90,6 @@ def __init__(self, nlt: NltResource) -> None:
@cached_property
def offers(self) -> OffersResourceWithStreamingResponse:
- """
- Dealer-aware reads of the shared NLT catalog — pricing reflects the dealer's markup and down-payment tiers.
- """
return OffersResourceWithStreamingResponse(self._nlt.offers)
@@ -114,7 +99,4 @@ def __init__(self, nlt: AsyncNltResource) -> None:
@cached_property
def offers(self) -> AsyncOffersResourceWithStreamingResponse:
- """
- Dealer-aware reads of the shared NLT catalog — pricing reflects the dealer's markup and down-payment tiers.
- """
return AsyncOffersResourceWithStreamingResponse(self._nlt.offers)
diff --git a/src/partnermax/resources/dealers/nlt/offers.py b/src/partnermax/resources/dealers/nlt/offers.py
index 40c7cbf..e4bf72e 100644
--- a/src/partnermax/resources/dealers/nlt/offers.py
+++ b/src/partnermax/resources/dealers/nlt/offers.py
@@ -2,7 +2,7 @@
from __future__ import annotations
-from typing_extensions import Literal
+from typing import Optional
import httpx
@@ -25,10 +25,6 @@
class OffersResource(SyncAPIResource):
- """
- Dealer-aware reads of the shared NLT catalog — pricing reflects the dealer's markup and down-payment tiers.
- """
-
@cached_property
def with_raw_response(self) -> OffersResourceWithRawResponse:
"""
@@ -60,11 +56,10 @@ def retrieve(
extra_body: Body | None = None,
timeout: float | httpx.Timeout | None | NotGiven = not_given,
) -> OfferRetrieveResponse:
- """
- Returns the full detail of one NLT offer including the 18-quotation matrix (3
- durations × 6 km/year tiers) with prices computed for each of the dealer's three
- down-payment tiers — 54 displayed price points total — plus included services,
- optional accessories, image gallery, and the dealer business card.
+ """Full offer detail.
+
+ Payload shape mirrors apimax MCP `get_nlt_offer_details`
+ bit-for-bit (mcp_server.py:1546-1606).
Args:
extra_headers: Send extra headers
@@ -91,14 +86,14 @@ def list(
self,
dealer_id: str,
*,
- brand: str | Omit = omit,
- canone_max_eur: int | Omit = omit,
- cursor: str | Omit = omit,
- duration_months: Literal[24, 36, 48] | Omit = omit,
- fuel_type: str | Omit = omit,
- km_per_year: Literal[10000, 15000, 20000, 25000, 30000, 40000] | Omit = omit,
+ brand: Optional[str] | Omit = omit,
+ canone_max_eur: Optional[int] | Omit = omit,
+ cursor: Optional[str] | Omit = omit,
+ duration_months: Optional[int] | Omit = omit,
+ fuel_type: Optional[str] | Omit = omit,
+ km_per_year: Optional[int] | Omit = omit,
limit: int | Omit = omit,
- segment: str | Omit = omit,
+ segment: Optional[str] | Omit = omit,
# Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
# The extra values given here take precedence over values defined on the client or passed to this method.
extra_headers: Headers | None = None,
@@ -106,24 +101,22 @@ def list(
extra_body: Body | None = None,
timeout: float | httpx.Timeout | None | NotGiven = not_given,
) -> OfferListResponse:
- """
- Returns a cursor-paginated list of NLT offers available to the specified dealer,
- with monthly canon already repriced using the dealer's markup and down-payment
- tiers.
+ """Listing of NLT offers with monthly canon repriced for this dealer.
- Args:
- brand: Filter by brand name, case-insensitive (e.g., `Fiat`).
-
- canone_max_eur: Upper bound on displayed monthly canon (EUR).
+ Strategy:
- cursor: Opaque pagination cursor.
+ 1.
- fuel_type: Raw Italian label (case-insensitive ILIKE match). Examples: "Benzina", "Diesel",
- "Ibrido benzina", "Ibrido diesel", "Elettrica", "GPL", "Metano".
-
- segment: Raw Italian label (case-insensitive ILIKE substring match). Examples: "SUV
- piccoli", "SUV medi", "Superiori", "Medie", "Utilitarie".
+ Resolve + ACL the dealer.
+ 2. Pull at most `limit + 1` offers from the catalog after the cursor. The extra
+ row lets us know if there's a next page without a second COUNT(\\**) query.
+ 3. Apply text/enum filters server-side via SQL where possible (brand, segment,
+ fuel) and the numeric `canone_max_eur` filter in Python after the pricing
+ pass (the DB has no "displayed canon" column; we synthesize it per dealer).
+ 4. For each surviving offer, price the (duration, km) cells the caller filtered
+ to (if specified) or all 18, pick the cheapest cell as the headline.
+ Args:
extra_headers: Send extra headers
extra_query: Add additional query parameters to the request
@@ -160,10 +153,6 @@ def list(
class AsyncOffersResource(AsyncAPIResource):
- """
- Dealer-aware reads of the shared NLT catalog — pricing reflects the dealer's markup and down-payment tiers.
- """
-
@cached_property
def with_raw_response(self) -> AsyncOffersResourceWithRawResponse:
"""
@@ -195,11 +184,10 @@ async def retrieve(
extra_body: Body | None = None,
timeout: float | httpx.Timeout | None | NotGiven = not_given,
) -> OfferRetrieveResponse:
- """
- Returns the full detail of one NLT offer including the 18-quotation matrix (3
- durations × 6 km/year tiers) with prices computed for each of the dealer's three
- down-payment tiers — 54 displayed price points total — plus included services,
- optional accessories, image gallery, and the dealer business card.
+ """Full offer detail.
+
+ Payload shape mirrors apimax MCP `get_nlt_offer_details`
+ bit-for-bit (mcp_server.py:1546-1606).
Args:
extra_headers: Send extra headers
@@ -226,14 +214,14 @@ async def list(
self,
dealer_id: str,
*,
- brand: str | Omit = omit,
- canone_max_eur: int | Omit = omit,
- cursor: str | Omit = omit,
- duration_months: Literal[24, 36, 48] | Omit = omit,
- fuel_type: str | Omit = omit,
- km_per_year: Literal[10000, 15000, 20000, 25000, 30000, 40000] | Omit = omit,
+ brand: Optional[str] | Omit = omit,
+ canone_max_eur: Optional[int] | Omit = omit,
+ cursor: Optional[str] | Omit = omit,
+ duration_months: Optional[int] | Omit = omit,
+ fuel_type: Optional[str] | Omit = omit,
+ km_per_year: Optional[int] | Omit = omit,
limit: int | Omit = omit,
- segment: str | Omit = omit,
+ segment: Optional[str] | Omit = omit,
# Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
# The extra values given here take precedence over values defined on the client or passed to this method.
extra_headers: Headers | None = None,
@@ -241,24 +229,22 @@ async def list(
extra_body: Body | None = None,
timeout: float | httpx.Timeout | None | NotGiven = not_given,
) -> OfferListResponse:
- """
- Returns a cursor-paginated list of NLT offers available to the specified dealer,
- with monthly canon already repriced using the dealer's markup and down-payment
- tiers.
+ """Listing of NLT offers with monthly canon repriced for this dealer.
- Args:
- brand: Filter by brand name, case-insensitive (e.g., `Fiat`).
-
- canone_max_eur: Upper bound on displayed monthly canon (EUR).
+ Strategy:
- cursor: Opaque pagination cursor.
+ 1.
- fuel_type: Raw Italian label (case-insensitive ILIKE match). Examples: "Benzina", "Diesel",
- "Ibrido benzina", "Ibrido diesel", "Elettrica", "GPL", "Metano".
-
- segment: Raw Italian label (case-insensitive ILIKE substring match). Examples: "SUV
- piccoli", "SUV medi", "Superiori", "Medie", "Utilitarie".
+ Resolve + ACL the dealer.
+ 2. Pull at most `limit + 1` offers from the catalog after the cursor. The extra
+ row lets us know if there's a next page without a second COUNT(\\**) query.
+ 3. Apply text/enum filters server-side via SQL where possible (brand, segment,
+ fuel) and the numeric `canone_max_eur` filter in Python after the pricing
+ pass (the DB has no "displayed canon" column; we synthesize it per dealer).
+ 4. For each surviving offer, price the (duration, km) cells the caller filtered
+ to (if specified) or all 18, pick the cheapest cell as the headline.
+ Args:
extra_headers: Send extra headers
extra_query: Add additional query parameters to the request
diff --git a/src/partnermax/resources/dealers/nlt_settings.py b/src/partnermax/resources/dealers/nlt_settings.py
index d7ce19d..7e50307 100644
--- a/src/partnermax/resources/dealers/nlt_settings.py
+++ b/src/partnermax/resources/dealers/nlt_settings.py
@@ -8,7 +8,7 @@
import httpx
from ..._types import Body, Omit, Query, Headers, NotGiven, omit, not_given
-from ..._utils import path_template, maybe_transform, strip_not_given, async_maybe_transform
+from ..._utils import path_template, maybe_transform, async_maybe_transform
from ..._compat import cached_property
from ..._resource import SyncAPIResource, AsyncAPIResource
from ..._response import (
@@ -26,8 +26,6 @@
class NltSettingsResource(SyncAPIResource):
- """Per-dealer NLT economics: agency markup percent and three down-payment tiers."""
-
@cached_property
def with_raw_response(self) -> NltSettingsResourceWithRawResponse:
"""
@@ -59,7 +57,7 @@ def retrieve(
timeout: float | httpx.Timeout | None | NotGiven = not_given,
) -> NltSettings:
"""
- Get current NLT economics for a dealer
+ Return current NLT economics for the dealer.
Args:
extra_headers: Send extra headers
@@ -86,10 +84,9 @@ def update(
*,
agency_markup_percent: float,
down_payment_tiers: DownPaymentTiersParam,
- image_mode: Literal["branded", "scenario_locked", "scenario_seasonal"],
currency: Literal["EUR"] | Omit = omit,
+ image_mode: Literal["branded", "scenario_locked", "scenario_seasonal"] | Omit = omit,
image_scenario_locked: Optional[Literal["mediterraneo", "cortina", "milano", "showroom"]] | Omit = omit,
- idempotency_key: str | Omit = omit,
# Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
# The extra values given here take precedence over values defined on the client or passed to this method.
extra_headers: Headers | None = None,
@@ -98,31 +95,40 @@ def update(
timeout: float | httpx.Timeout | None | NotGiven = not_given,
) -> NltSettings:
"""
- Sets the dealer's agency markup percent (0–10) and three down-payment tiers (low
- / medium / high). Each tier carries `percent_of_list (0–100)` +
- `fixed_eur (≥0)`; the final EUR per tier is offer-dependent
- (`listino_imponibile * pct + eur`). Changes propagate to the cross-network AI
- surfaces within five minutes.
+ Set markup percent (0-10) and three down-payment tiers (strictly ascending).
+
+ Validation:
+
+ - `agency_markup_percent` ∈ [0, 10] (Pydantic).
+ - `down_payment_tiers.{low,medium,high}` each
+ `{percent_of_list (0–100), fixed_eur (≥0)}`. No strict-ascending check — the
+ final EUR per tier is offer-dependent (`listino_imponibile * pct + eur`).
- The displayed monthly canon for an offer is computed as:
+ Persistence:
- ```
- listino_imponibile = prezzo_listino / 1.22
- provvigione = listino_imponibile × (agency_markup_percent / 100)
- anticipo_tier_eur = listino_imponibile × (tier.percent_of_list / 100) + tier.fixed_eur
- canon = base_canon + provvigione / duration_months − anticipo_tier_eur / duration_months
- if offer.private_only: canon *= 1.22
- ```
+ - `agency_markup_percent` → `dealer_public.nlt_agency_percent` (rounded to int;
+ live column is `Integer NOT NULL DEFAULT 2`).
+ - `down_payment_tiers` → `dealer_public.nlt_anticipi_config` JSONB, stored in
+ apimax shape `[{"pct": <0..1>, "eur": }, ...]`. The partner-facing
+ `percent_of_list` (0–100) is divided by 100 to keep the column byte-compatible
+ with the DealerMAX UI calculator that reads the same JSONB.
- VAT treatment is a property of each offer (`NltOfferDetail.private_only` /
- `NltOfferSummary.vat_treatment`), not of the dealer.
+ There is NO `vat_treatment` field: VAT is per-offer (`nlt_offerte.solo_privati`)
+ in the canonical DataMax pricing model, not per-dealer. The offer detail
+ endpoint surfaces it per row instead.
+
+ The Idempotency middleware handles `Idempotency-Key` replay outside this
+ handler; we just need to make the write itself idempotent at the row level (a
+ re-applied identical PATCH is a no-op by construction).
Args:
- down_payment_tiers: Three down-payment scenarios (basso / medio / alto). Each tier carries
- `{percent_of_list (0–100), fixed_eur (≥0)}`. No strict-ascending check — the
- final EUR per tier is offer-dependent (`listino_imponibile * pct + eur`).
+ down_payment_tiers: Three down-payment scenarios (basso / medio / alto).
- image_scenario_locked: Required when `image_mode='scenario_locked'`; must be null otherwise.
+ No strict-ascending validation: the final EUR amount depends on the offer's list
+ price (`tier.percent_of_list / 100 * listino_imponibile + tier.fixed_eur`), so a
+ tier that looks larger by % can produce a smaller EUR on cheap vehicles. Label
+ semantics (low/medium/high) are advisory — apimax/DealerMAX UI treats the 3
+ positions as opaque slots ordered by intent.
extra_headers: Send extra headers
@@ -134,15 +140,14 @@ def update(
"""
if not dealer_id:
raise ValueError(f"Expected a non-empty value for `dealer_id` but received {dealer_id!r}")
- extra_headers = {**strip_not_given({"Idempotency-Key": idempotency_key}), **(extra_headers or {})}
return self._patch(
path_template("/v1/dealers/{dealer_id}/nlt-settings", dealer_id=dealer_id),
body=maybe_transform(
{
"agency_markup_percent": agency_markup_percent,
"down_payment_tiers": down_payment_tiers,
- "image_mode": image_mode,
"currency": currency,
+ "image_mode": image_mode,
"image_scenario_locked": image_scenario_locked,
},
nlt_setting_update_params.NltSettingUpdateParams,
@@ -155,8 +160,6 @@ def update(
class AsyncNltSettingsResource(AsyncAPIResource):
- """Per-dealer NLT economics: agency markup percent and three down-payment tiers."""
-
@cached_property
def with_raw_response(self) -> AsyncNltSettingsResourceWithRawResponse:
"""
@@ -188,7 +191,7 @@ async def retrieve(
timeout: float | httpx.Timeout | None | NotGiven = not_given,
) -> NltSettings:
"""
- Get current NLT economics for a dealer
+ Return current NLT economics for the dealer.
Args:
extra_headers: Send extra headers
@@ -215,10 +218,9 @@ async def update(
*,
agency_markup_percent: float,
down_payment_tiers: DownPaymentTiersParam,
- image_mode: Literal["branded", "scenario_locked", "scenario_seasonal"],
currency: Literal["EUR"] | Omit = omit,
+ image_mode: Literal["branded", "scenario_locked", "scenario_seasonal"] | Omit = omit,
image_scenario_locked: Optional[Literal["mediterraneo", "cortina", "milano", "showroom"]] | Omit = omit,
- idempotency_key: str | Omit = omit,
# Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
# The extra values given here take precedence over values defined on the client or passed to this method.
extra_headers: Headers | None = None,
@@ -227,31 +229,40 @@ async def update(
timeout: float | httpx.Timeout | None | NotGiven = not_given,
) -> NltSettings:
"""
- Sets the dealer's agency markup percent (0–10) and three down-payment tiers (low
- / medium / high). Each tier carries `percent_of_list (0–100)` +
- `fixed_eur (≥0)`; the final EUR per tier is offer-dependent
- (`listino_imponibile * pct + eur`). Changes propagate to the cross-network AI
- surfaces within five minutes.
+ Set markup percent (0-10) and three down-payment tiers (strictly ascending).
+
+ Validation:
+
+ - `agency_markup_percent` ∈ [0, 10] (Pydantic).
+ - `down_payment_tiers.{low,medium,high}` each
+ `{percent_of_list (0–100), fixed_eur (≥0)}`. No strict-ascending check — the
+ final EUR per tier is offer-dependent (`listino_imponibile * pct + eur`).
- The displayed monthly canon for an offer is computed as:
+ Persistence:
- ```
- listino_imponibile = prezzo_listino / 1.22
- provvigione = listino_imponibile × (agency_markup_percent / 100)
- anticipo_tier_eur = listino_imponibile × (tier.percent_of_list / 100) + tier.fixed_eur
- canon = base_canon + provvigione / duration_months − anticipo_tier_eur / duration_months
- if offer.private_only: canon *= 1.22
- ```
+ - `agency_markup_percent` → `dealer_public.nlt_agency_percent` (rounded to int;
+ live column is `Integer NOT NULL DEFAULT 2`).
+ - `down_payment_tiers` → `dealer_public.nlt_anticipi_config` JSONB, stored in
+ apimax shape `[{"pct": <0..1>, "eur": }, ...]`. The partner-facing
+ `percent_of_list` (0–100) is divided by 100 to keep the column byte-compatible
+ with the DealerMAX UI calculator that reads the same JSONB.
- VAT treatment is a property of each offer (`NltOfferDetail.private_only` /
- `NltOfferSummary.vat_treatment`), not of the dealer.
+ There is NO `vat_treatment` field: VAT is per-offer (`nlt_offerte.solo_privati`)
+ in the canonical DataMax pricing model, not per-dealer. The offer detail
+ endpoint surfaces it per row instead.
+
+ The Idempotency middleware handles `Idempotency-Key` replay outside this
+ handler; we just need to make the write itself idempotent at the row level (a
+ re-applied identical PATCH is a no-op by construction).
Args:
- down_payment_tiers: Three down-payment scenarios (basso / medio / alto). Each tier carries
- `{percent_of_list (0–100), fixed_eur (≥0)}`. No strict-ascending check — the
- final EUR per tier is offer-dependent (`listino_imponibile * pct + eur`).
+ down_payment_tiers: Three down-payment scenarios (basso / medio / alto).
- image_scenario_locked: Required when `image_mode='scenario_locked'`; must be null otherwise.
+ No strict-ascending validation: the final EUR amount depends on the offer's list
+ price (`tier.percent_of_list / 100 * listino_imponibile + tier.fixed_eur`), so a
+ tier that looks larger by % can produce a smaller EUR on cheap vehicles. Label
+ semantics (low/medium/high) are advisory — apimax/DealerMAX UI treats the 3
+ positions as opaque slots ordered by intent.
extra_headers: Send extra headers
@@ -263,15 +274,14 @@ async def update(
"""
if not dealer_id:
raise ValueError(f"Expected a non-empty value for `dealer_id` but received {dealer_id!r}")
- extra_headers = {**strip_not_given({"Idempotency-Key": idempotency_key}), **(extra_headers or {})}
return await self._patch(
path_template("/v1/dealers/{dealer_id}/nlt-settings", dealer_id=dealer_id),
body=await async_maybe_transform(
{
"agency_markup_percent": agency_markup_percent,
"down_payment_tiers": down_payment_tiers,
- "image_mode": image_mode,
"currency": currency,
+ "image_mode": image_mode,
"image_scenario_locked": image_scenario_locked,
},
nlt_setting_update_params.NltSettingUpdateParams,
diff --git a/src/partnermax/resources/dealers/vehicles.py b/src/partnermax/resources/dealers/vehicles.py
new file mode 100644
index 0000000..99332b1
--- /dev/null
+++ b/src/partnermax/resources/dealers/vehicles.py
@@ -0,0 +1,1080 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from typing import Union, Iterable, Optional
+from datetime import date
+
+import httpx
+
+from ..._types import Body, Omit, Query, Headers, NoneType, NotGiven, omit, not_given
+from ..._utils import path_template, maybe_transform, strip_not_given, async_maybe_transform
+from ..._compat import cached_property
+from ..._resource import SyncAPIResource, AsyncAPIResource
+from ..._response import (
+ to_raw_response_wrapper,
+ to_streamed_response_wrapper,
+ async_to_raw_response_wrapper,
+ async_to_streamed_response_wrapper,
+)
+from ..._base_client import make_request_options
+from ...types.dealers import (
+ vehicle_bulk_params,
+ vehicle_list_params,
+ vehicle_create_params,
+ vehicle_update_params,
+ vehicle_retrieve_params,
+)
+from ...types.dealers.vehicle_list import VehicleList
+from ...types.dealers.vehicle_detail import VehicleDetail
+from ...types.dealers.bulk_create_vehicles_response import BulkCreateVehiclesResponse
+
+__all__ = ["VehiclesResource", "AsyncVehiclesResource"]
+
+
+class VehiclesResource(SyncAPIResource):
+ """Used-vehicle stock management for partner-owned dealers.
+
+ The partner uploads each used vehicle by its canonical Motornet UNI code; DealerMAX joins the partner-provided pricing and stock metadata with the catalog master so the resulting listing is immediately indexed by the AI surfaces (MCP server, ChatGPT Custom GPT, NLWeb /ask, and the SEO/JSON-LD layer).
+ """
+
+ @cached_property
+ def with_raw_response(self) -> VehiclesResourceWithRawResponse:
+ """
+ This property can be used as a prefix for any HTTP method call to return
+ the raw response object instead of the parsed content.
+
+ For more information, see https://www.github.com/DealerMax-app/partnermax-python#accessing-raw-response-data-eg-headers
+ """
+ return VehiclesResourceWithRawResponse(self)
+
+ @cached_property
+ def with_streaming_response(self) -> VehiclesResourceWithStreamingResponse:
+ """
+ An alternative to `.with_raw_response` that doesn't eagerly read the response body.
+
+ For more information, see https://www.github.com/DealerMax-app/partnermax-python#with_streaming_response
+ """
+ return VehiclesResourceWithStreamingResponse(self)
+
+ def create(
+ self,
+ dealer_id: str,
+ *,
+ certified_km: int,
+ cost_price_eur: float,
+ motornet_code: str,
+ plate: str,
+ registration_year: int,
+ sale_price_eur: float,
+ alloy_wheel_size: Optional[int] | Omit = omit,
+ color: Optional[str] | Omit = omit,
+ description: str | Omit = omit,
+ extended_warranty_enabled: bool | Omit = omit,
+ extended_warranty_months: Optional[int] | Omit = omit,
+ inspection_expiry_date: Union[str, date, None] | Omit = omit,
+ is_for_sale: bool | Omit = omit,
+ is_visible: bool | Omit = omit,
+ last_service_date: Union[str, date, None] | Omit = omit,
+ last_service_km: Optional[int] | Omit = omit,
+ last_service_notes: Optional[str] | Omit = omit,
+ notes: Optional[str] | Omit = omit,
+ previous_owner_count: Optional[int] | Omit = omit,
+ previous_ownership_transfer_date: Union[str, date, None] | Omit = omit,
+ registration_month: Optional[int] | Omit = omit,
+ road_tax_expiry_date: Union[str, date, None] | Omit = omit,
+ vat_displayed: bool | Omit = omit,
+ vehicle_damaged: bool | Omit = omit,
+ vin: Optional[str] | Omit = omit,
+ idempotency_key: str | Omit = omit,
+ # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+ # The extra values given here take precedence over values defined on the client or passed to this method.
+ extra_headers: Headers | None = None,
+ extra_query: Query | None = None,
+ extra_body: Body | None = None,
+ timeout: float | httpx.Timeout | None | NotGiven = not_given,
+ ) -> VehicleDetail:
+ """
+ Provision a new used vehicle in a dealer's stock.
+
+ Writes are atomic across `azlease_usatoin` and `azlease_usatoauto` using a
+ `SAVEPOINT` so a UNIQUE plate violation rolls back cleanly. On success the
+ AI-content worker (:mod:`azurenet-engine.app.jobs.usato_ai_content_worker`)
+ picks up the new row within 60 seconds and generates the SEO body + pgvector
+ embedding — at which point the vehicle becomes discoverable on the cross-network
+ MCP / Custom GPT / NLWeb surfaces. The response returns immediately (no
+ synchronous wait on the worker).
+
+ Args:
+ certified_km: Certified odometer reading at intake, in kilometres.
+
+ cost_price_eur: Cost basis to the dealer in EUR (partner/dealer internal). Not surfaced on
+ consumer-facing AI surfaces; used by dealer reporting and margin analytics only.
+
+ motornet_code: Motornet UNI code identifying the exact vehicle configuration. Must exist in
+ `mnet_dettagli_usato` at submission time; otherwise the call returns 422
+ `motornet_code_not_in_catalogue`. The partner is expected to source this from
+ its own DMS; partnermax does not expose a plate→code lookup.
+
+ plate: Italian licence plate. Uppercased server-side. UNIQUE across the network for
+ active vehicles (`visibile=true AND venduto_il IS NULL`); reusable once the
+ previous holder sells/hides the row.
+
+ registration_year: Year of first registration. Upper bound is current year + 1.
+
+ sale_price_eur: Public sale price in EUR. Surfaced on MCP / Custom GPT / NLWeb and on the
+ dealer's site JSON-LD `Offer.price`.
+
+ description: Partner-supplied long description. Surfaced on the dealer site detail page.
+
+ is_for_sale: Maps to `azlease_usatoauto.is_vendita_enabled`. When false the row is in stock
+ but not offered for sale.
+
+ is_visible: Soft-publish flag. When false the row exists in stock but is excluded from
+ consumer-facing AI surfaces. Maps to `azlease_usatoin.visibile`.
+
+ notes: Free-form short notes; surfaced as `mnet_dettagli.precisazioni`-style.
+
+ previous_ownership_transfer_date: Date of the most recent ownership transfer, if known.
+
+ registration_month: Month of registration (1–12).
+
+ vat_displayed: If true the public price is displayed VAT-exposed (B2B); otherwise VAT-inclusive
+ (B2C).
+
+ vin: ISO 3779 vehicle identification number. Optional but strongly recommended.
+
+ extra_headers: Send extra headers
+
+ extra_query: Add additional query parameters to the request
+
+ extra_body: Add additional JSON properties to the request
+
+ timeout: Override the client-level default timeout for this request, in seconds
+ """
+ if not dealer_id:
+ raise ValueError(f"Expected a non-empty value for `dealer_id` but received {dealer_id!r}")
+ extra_headers = {**strip_not_given({"Idempotency-Key": idempotency_key}), **(extra_headers or {})}
+ return self._post(
+ path_template("/v1/dealers/{dealer_id}/vehicles", dealer_id=dealer_id),
+ body=maybe_transform(
+ {
+ "certified_km": certified_km,
+ "cost_price_eur": cost_price_eur,
+ "motornet_code": motornet_code,
+ "plate": plate,
+ "registration_year": registration_year,
+ "sale_price_eur": sale_price_eur,
+ "alloy_wheel_size": alloy_wheel_size,
+ "color": color,
+ "description": description,
+ "extended_warranty_enabled": extended_warranty_enabled,
+ "extended_warranty_months": extended_warranty_months,
+ "inspection_expiry_date": inspection_expiry_date,
+ "is_for_sale": is_for_sale,
+ "is_visible": is_visible,
+ "last_service_date": last_service_date,
+ "last_service_km": last_service_km,
+ "last_service_notes": last_service_notes,
+ "notes": notes,
+ "previous_owner_count": previous_owner_count,
+ "previous_ownership_transfer_date": previous_ownership_transfer_date,
+ "registration_month": registration_month,
+ "road_tax_expiry_date": road_tax_expiry_date,
+ "vat_displayed": vat_displayed,
+ "vehicle_damaged": vehicle_damaged,
+ "vin": vin,
+ },
+ vehicle_create_params.VehicleCreateParams,
+ ),
+ options=make_request_options(
+ extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+ ),
+ cast_to=VehicleDetail,
+ )
+
+ def retrieve(
+ self,
+ vehicle_id: str,
+ *,
+ dealer_id: str,
+ include_deleted: bool | Omit = omit,
+ # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+ # The extra values given here take precedence over values defined on the client or passed to this method.
+ extra_headers: Headers | None = None,
+ extra_query: Query | None = None,
+ extra_body: Body | None = None,
+ timeout: float | httpx.Timeout | None | NotGiven = not_given,
+ ) -> VehicleDetail:
+ """
+ Get Vehicle
+
+ Args:
+ include_deleted: If true, the detail of a soft-deleted vehicle is returned. Default false —
+ soft-deleted rows return 404 to keep behaviour consistent with the list
+ endpoint.
+
+ extra_headers: Send extra headers
+
+ extra_query: Add additional query parameters to the request
+
+ extra_body: Add additional JSON properties to the request
+
+ timeout: Override the client-level default timeout for this request, in seconds
+ """
+ if not dealer_id:
+ raise ValueError(f"Expected a non-empty value for `dealer_id` but received {dealer_id!r}")
+ if not vehicle_id:
+ raise ValueError(f"Expected a non-empty value for `vehicle_id` but received {vehicle_id!r}")
+ return self._get(
+ path_template("/v1/dealers/{dealer_id}/vehicles/{vehicle_id}", dealer_id=dealer_id, vehicle_id=vehicle_id),
+ options=make_request_options(
+ extra_headers=extra_headers,
+ extra_query=extra_query,
+ extra_body=extra_body,
+ timeout=timeout,
+ query=maybe_transform(
+ {"include_deleted": include_deleted}, vehicle_retrieve_params.VehicleRetrieveParams
+ ),
+ ),
+ cast_to=VehicleDetail,
+ )
+
+ def update(
+ self,
+ vehicle_id: str,
+ *,
+ dealer_id: str,
+ alloy_wheel_size: Optional[int] | Omit = omit,
+ certified_km: Optional[int] | Omit = omit,
+ color: Optional[str] | Omit = omit,
+ cost_price_eur: Optional[float] | Omit = omit,
+ description: Optional[str] | Omit = omit,
+ extended_warranty_enabled: Optional[bool] | Omit = omit,
+ extended_warranty_months: Optional[int] | Omit = omit,
+ inspection_expiry_date: Union[str, date, None] | Omit = omit,
+ is_for_sale: Optional[bool] | Omit = omit,
+ is_visible: Optional[bool] | Omit = omit,
+ last_service_date: Union[str, date, None] | Omit = omit,
+ last_service_km: Optional[int] | Omit = omit,
+ last_service_notes: Optional[str] | Omit = omit,
+ notes: Optional[str] | Omit = omit,
+ previous_owner_count: Optional[int] | Omit = omit,
+ previous_ownership_transfer_date: Union[str, date, None] | Omit = omit,
+ registration_month: Optional[int] | Omit = omit,
+ road_tax_expiry_date: Union[str, date, None] | Omit = omit,
+ sale_price_eur: Optional[float] | Omit = omit,
+ vat_displayed: Optional[bool] | Omit = omit,
+ vehicle_damaged: Optional[bool] | Omit = omit,
+ idempotency_key: str | Omit = omit,
+ # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+ # The extra values given here take precedence over values defined on the client or passed to this method.
+ extra_headers: Headers | None = None,
+ extra_query: Query | None = None,
+ extra_body: Body | None = None,
+ timeout: float | httpx.Timeout | None | NotGiven = not_given,
+ ) -> VehicleDetail:
+ """
+ Partial update of a vehicle.
+
+ Splits the inbound body across the two physical tables (`azlease_usatoauto` and
+ `azlease_usatoin`) and emits at most one UPDATE per table inside a single
+ transaction. Fields not present in the body are not touched.
+
+ Args:
+ extra_headers: Send extra headers
+
+ extra_query: Add additional query parameters to the request
+
+ extra_body: Add additional JSON properties to the request
+
+ timeout: Override the client-level default timeout for this request, in seconds
+ """
+ if not dealer_id:
+ raise ValueError(f"Expected a non-empty value for `dealer_id` but received {dealer_id!r}")
+ if not vehicle_id:
+ raise ValueError(f"Expected a non-empty value for `vehicle_id` but received {vehicle_id!r}")
+ extra_headers = {**strip_not_given({"Idempotency-Key": idempotency_key}), **(extra_headers or {})}
+ return self._patch(
+ path_template("/v1/dealers/{dealer_id}/vehicles/{vehicle_id}", dealer_id=dealer_id, vehicle_id=vehicle_id),
+ body=maybe_transform(
+ {
+ "alloy_wheel_size": alloy_wheel_size,
+ "certified_km": certified_km,
+ "color": color,
+ "cost_price_eur": cost_price_eur,
+ "description": description,
+ "extended_warranty_enabled": extended_warranty_enabled,
+ "extended_warranty_months": extended_warranty_months,
+ "inspection_expiry_date": inspection_expiry_date,
+ "is_for_sale": is_for_sale,
+ "is_visible": is_visible,
+ "last_service_date": last_service_date,
+ "last_service_km": last_service_km,
+ "last_service_notes": last_service_notes,
+ "notes": notes,
+ "previous_owner_count": previous_owner_count,
+ "previous_ownership_transfer_date": previous_ownership_transfer_date,
+ "registration_month": registration_month,
+ "road_tax_expiry_date": road_tax_expiry_date,
+ "sale_price_eur": sale_price_eur,
+ "vat_displayed": vat_displayed,
+ "vehicle_damaged": vehicle_damaged,
+ },
+ vehicle_update_params.VehicleUpdateParams,
+ ),
+ options=make_request_options(
+ extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+ ),
+ cast_to=VehicleDetail,
+ )
+
+ def list(
+ self,
+ dealer_id: str,
+ *,
+ cursor: Optional[str] | Omit = omit,
+ include_deleted: bool | Omit = omit,
+ is_for_sale: Optional[bool] | Omit = omit,
+ is_visible: Optional[bool] | Omit = omit,
+ limit: int | Omit = omit,
+ # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+ # The extra values given here take precedence over values defined on the client or passed to this method.
+ extra_headers: Headers | None = None,
+ extra_query: Query | None = None,
+ extra_body: Body | None = None,
+ timeout: float | httpx.Timeout | None | NotGiven = not_given,
+ ) -> VehicleList:
+ """
+ List vehicles in a dealer's stock owned by the calling partner.
+
+ Cursor pagination is opaque base64url over the last vehicle UUID. Default sort
+ is `i.data_inserimento ASC` so freshly provisioned vehicles surface at the tail.
+ Soft-deleted rows are excluded unless `include_deleted=true` is set explicitly —
+ this preserves the soft-delete semantic across the API contract.
+
+ Args:
+ include_deleted: If true, soft-deleted rows (`venduto_il` populated) are also returned. Default
+ false — listings hide soft-deleted vehicles.
+
+ is_for_sale: Filter on the sale flag.
+
+ is_visible: Filter on the visibility flag.
+
+ extra_headers: Send extra headers
+
+ extra_query: Add additional query parameters to the request
+
+ extra_body: Add additional JSON properties to the request
+
+ timeout: Override the client-level default timeout for this request, in seconds
+ """
+ if not dealer_id:
+ raise ValueError(f"Expected a non-empty value for `dealer_id` but received {dealer_id!r}")
+ return self._get(
+ path_template("/v1/dealers/{dealer_id}/vehicles", dealer_id=dealer_id),
+ options=make_request_options(
+ extra_headers=extra_headers,
+ extra_query=extra_query,
+ extra_body=extra_body,
+ timeout=timeout,
+ query=maybe_transform(
+ {
+ "cursor": cursor,
+ "include_deleted": include_deleted,
+ "is_for_sale": is_for_sale,
+ "is_visible": is_visible,
+ "limit": limit,
+ },
+ vehicle_list_params.VehicleListParams,
+ ),
+ ),
+ cast_to=VehicleList,
+ )
+
+ def delete(
+ self,
+ vehicle_id: str,
+ *,
+ dealer_id: str,
+ # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+ # The extra values given here take precedence over values defined on the client or passed to this method.
+ extra_headers: Headers | None = None,
+ extra_query: Query | None = None,
+ extra_body: Body | None = None,
+ timeout: float | httpx.Timeout | None | NotGiven = not_given,
+ ) -> None:
+ """
+ Withdraw a vehicle from sale without deleting the row.
+
+ Sets `azlease_usatoin.visibile = FALSE` and stamps `venduto_il = now()`. The
+ plate becomes reusable on the network the moment this returns (the
+ active-uniqueness check excludes rows where `visibile = FALSE` OR
+ `venduto_il IS NOT NULL`).
+
+ Soft-delete is the canonical "remove this vehicle from sale" surface. The
+ AI-citation consumers (MCP `_tool_search_vehicles`, Custom GPT
+ `search_vehicles_network`, NLWeb `/ask`) each filter their own queries on
+ `i.visibile = TRUE AND i.venduto_il IS NULL` — the shared `v_apimax_listing`
+ view itself does not impose that filter, every consumer adds it. The result on
+ the partner side is the same: a soft-deleted vehicle disappears from every AI
+ surface within the next index cycle.
+
+ Returns `409 vehicle_already_deleted` if the row is already soft- deleted — same
+ idempotency pattern as the dealers DELETE endpoint.
+
+ Args:
+ extra_headers: Send extra headers
+
+ extra_query: Add additional query parameters to the request
+
+ extra_body: Add additional JSON properties to the request
+
+ timeout: Override the client-level default timeout for this request, in seconds
+ """
+ if not dealer_id:
+ raise ValueError(f"Expected a non-empty value for `dealer_id` but received {dealer_id!r}")
+ if not vehicle_id:
+ raise ValueError(f"Expected a non-empty value for `vehicle_id` but received {vehicle_id!r}")
+ extra_headers = {"Accept": "*/*", **(extra_headers or {})}
+ return self._delete(
+ path_template("/v1/dealers/{dealer_id}/vehicles/{vehicle_id}", dealer_id=dealer_id, vehicle_id=vehicle_id),
+ options=make_request_options(
+ extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+ ),
+ cast_to=NoneType,
+ )
+
+ def bulk(
+ self,
+ dealer_id: str,
+ *,
+ vehicles: Iterable[vehicle_bulk_params.Vehicle],
+ idempotency_key: str | Omit = omit,
+ # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+ # The extra values given here take precedence over values defined on the client or passed to this method.
+ extra_headers: Headers | None = None,
+ extra_query: Query | None = None,
+ extra_body: Body | None = None,
+ timeout: float | httpx.Timeout | None | NotGiven = not_given,
+ ) -> BulkCreateVehiclesResponse:
+ """
+ Provision up to `BULK_MAX_ROWS` vehicles in a single synchronous call.
+
+ Each row is processed inside its own `SAVEPOINT` so a failure on row N
+ (validation, plate conflict, motornet not in catalogue, race) is isolated — the
+ SAVEPOINT rolls back, the per-row outcome is collected with a structured error
+ code, and the loop continues with row N+1.
+
+ Successful rows accumulate in the outer transaction and are committed together
+ at the end of the request. Failed rows leave no trace in the database.
+
+ Returns `207 Multi-Status`. The response carries:
+
+ - `total`, `succeeded`, `failed` — aggregate counters for quick branch logic on
+ the partner side.
+ - `results` — array of per-row outcomes, indexed by the position in the request
+ `vehicles[]` array. Successful rows include the full `VehicleDetail`; failed
+ rows include `error_code` + `error_message` keyed to the same codes as the
+ single-POST surface so the partner reuses one error handler for both paths.
+
+ For imports larger than `BULK_MAX_ROWS` (currently 100), the partner is expected
+ to chunk the array client-side. A 5 000-vehicle initial migration is 50 calls;
+ the partner controls concurrency.
+
+ Args:
+ vehicles: Array of vehicles to create. Between 1 and 100 rows per call. For larger
+ imports, the partner is expected to chunk client-side (e.g. 50 calls of 100 rows
+ each for a 5 000-vehicle migration).
+
+ extra_headers: Send extra headers
+
+ extra_query: Add additional query parameters to the request
+
+ extra_body: Add additional JSON properties to the request
+
+ timeout: Override the client-level default timeout for this request, in seconds
+ """
+ if not dealer_id:
+ raise ValueError(f"Expected a non-empty value for `dealer_id` but received {dealer_id!r}")
+ extra_headers = {**strip_not_given({"Idempotency-Key": idempotency_key}), **(extra_headers or {})}
+ return self._post(
+ path_template("/v1/dealers/{dealer_id}/vehicles/bulk", dealer_id=dealer_id),
+ body=maybe_transform({"vehicles": vehicles}, vehicle_bulk_params.VehicleBulkParams),
+ options=make_request_options(
+ extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+ ),
+ cast_to=BulkCreateVehiclesResponse,
+ )
+
+
+class AsyncVehiclesResource(AsyncAPIResource):
+ """Used-vehicle stock management for partner-owned dealers.
+
+ The partner uploads each used vehicle by its canonical Motornet UNI code; DealerMAX joins the partner-provided pricing and stock metadata with the catalog master so the resulting listing is immediately indexed by the AI surfaces (MCP server, ChatGPT Custom GPT, NLWeb /ask, and the SEO/JSON-LD layer).
+ """
+
+ @cached_property
+ def with_raw_response(self) -> AsyncVehiclesResourceWithRawResponse:
+ """
+ This property can be used as a prefix for any HTTP method call to return
+ the raw response object instead of the parsed content.
+
+ For more information, see https://www.github.com/DealerMax-app/partnermax-python#accessing-raw-response-data-eg-headers
+ """
+ return AsyncVehiclesResourceWithRawResponse(self)
+
+ @cached_property
+ def with_streaming_response(self) -> AsyncVehiclesResourceWithStreamingResponse:
+ """
+ An alternative to `.with_raw_response` that doesn't eagerly read the response body.
+
+ For more information, see https://www.github.com/DealerMax-app/partnermax-python#with_streaming_response
+ """
+ return AsyncVehiclesResourceWithStreamingResponse(self)
+
+ async def create(
+ self,
+ dealer_id: str,
+ *,
+ certified_km: int,
+ cost_price_eur: float,
+ motornet_code: str,
+ plate: str,
+ registration_year: int,
+ sale_price_eur: float,
+ alloy_wheel_size: Optional[int] | Omit = omit,
+ color: Optional[str] | Omit = omit,
+ description: str | Omit = omit,
+ extended_warranty_enabled: bool | Omit = omit,
+ extended_warranty_months: Optional[int] | Omit = omit,
+ inspection_expiry_date: Union[str, date, None] | Omit = omit,
+ is_for_sale: bool | Omit = omit,
+ is_visible: bool | Omit = omit,
+ last_service_date: Union[str, date, None] | Omit = omit,
+ last_service_km: Optional[int] | Omit = omit,
+ last_service_notes: Optional[str] | Omit = omit,
+ notes: Optional[str] | Omit = omit,
+ previous_owner_count: Optional[int] | Omit = omit,
+ previous_ownership_transfer_date: Union[str, date, None] | Omit = omit,
+ registration_month: Optional[int] | Omit = omit,
+ road_tax_expiry_date: Union[str, date, None] | Omit = omit,
+ vat_displayed: bool | Omit = omit,
+ vehicle_damaged: bool | Omit = omit,
+ vin: Optional[str] | Omit = omit,
+ idempotency_key: str | Omit = omit,
+ # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+ # The extra values given here take precedence over values defined on the client or passed to this method.
+ extra_headers: Headers | None = None,
+ extra_query: Query | None = None,
+ extra_body: Body | None = None,
+ timeout: float | httpx.Timeout | None | NotGiven = not_given,
+ ) -> VehicleDetail:
+ """
+ Provision a new used vehicle in a dealer's stock.
+
+ Writes are atomic across `azlease_usatoin` and `azlease_usatoauto` using a
+ `SAVEPOINT` so a UNIQUE plate violation rolls back cleanly. On success the
+ AI-content worker (:mod:`azurenet-engine.app.jobs.usato_ai_content_worker`)
+ picks up the new row within 60 seconds and generates the SEO body + pgvector
+ embedding — at which point the vehicle becomes discoverable on the cross-network
+ MCP / Custom GPT / NLWeb surfaces. The response returns immediately (no
+ synchronous wait on the worker).
+
+ Args:
+ certified_km: Certified odometer reading at intake, in kilometres.
+
+ cost_price_eur: Cost basis to the dealer in EUR (partner/dealer internal). Not surfaced on
+ consumer-facing AI surfaces; used by dealer reporting and margin analytics only.
+
+ motornet_code: Motornet UNI code identifying the exact vehicle configuration. Must exist in
+ `mnet_dettagli_usato` at submission time; otherwise the call returns 422
+ `motornet_code_not_in_catalogue`. The partner is expected to source this from
+ its own DMS; partnermax does not expose a plate→code lookup.
+
+ plate: Italian licence plate. Uppercased server-side. UNIQUE across the network for
+ active vehicles (`visibile=true AND venduto_il IS NULL`); reusable once the
+ previous holder sells/hides the row.
+
+ registration_year: Year of first registration. Upper bound is current year + 1.
+
+ sale_price_eur: Public sale price in EUR. Surfaced on MCP / Custom GPT / NLWeb and on the
+ dealer's site JSON-LD `Offer.price`.
+
+ description: Partner-supplied long description. Surfaced on the dealer site detail page.
+
+ is_for_sale: Maps to `azlease_usatoauto.is_vendita_enabled`. When false the row is in stock
+ but not offered for sale.
+
+ is_visible: Soft-publish flag. When false the row exists in stock but is excluded from
+ consumer-facing AI surfaces. Maps to `azlease_usatoin.visibile`.
+
+ notes: Free-form short notes; surfaced as `mnet_dettagli.precisazioni`-style.
+
+ previous_ownership_transfer_date: Date of the most recent ownership transfer, if known.
+
+ registration_month: Month of registration (1–12).
+
+ vat_displayed: If true the public price is displayed VAT-exposed (B2B); otherwise VAT-inclusive
+ (B2C).
+
+ vin: ISO 3779 vehicle identification number. Optional but strongly recommended.
+
+ extra_headers: Send extra headers
+
+ extra_query: Add additional query parameters to the request
+
+ extra_body: Add additional JSON properties to the request
+
+ timeout: Override the client-level default timeout for this request, in seconds
+ """
+ if not dealer_id:
+ raise ValueError(f"Expected a non-empty value for `dealer_id` but received {dealer_id!r}")
+ extra_headers = {**strip_not_given({"Idempotency-Key": idempotency_key}), **(extra_headers or {})}
+ return await self._post(
+ path_template("/v1/dealers/{dealer_id}/vehicles", dealer_id=dealer_id),
+ body=await async_maybe_transform(
+ {
+ "certified_km": certified_km,
+ "cost_price_eur": cost_price_eur,
+ "motornet_code": motornet_code,
+ "plate": plate,
+ "registration_year": registration_year,
+ "sale_price_eur": sale_price_eur,
+ "alloy_wheel_size": alloy_wheel_size,
+ "color": color,
+ "description": description,
+ "extended_warranty_enabled": extended_warranty_enabled,
+ "extended_warranty_months": extended_warranty_months,
+ "inspection_expiry_date": inspection_expiry_date,
+ "is_for_sale": is_for_sale,
+ "is_visible": is_visible,
+ "last_service_date": last_service_date,
+ "last_service_km": last_service_km,
+ "last_service_notes": last_service_notes,
+ "notes": notes,
+ "previous_owner_count": previous_owner_count,
+ "previous_ownership_transfer_date": previous_ownership_transfer_date,
+ "registration_month": registration_month,
+ "road_tax_expiry_date": road_tax_expiry_date,
+ "vat_displayed": vat_displayed,
+ "vehicle_damaged": vehicle_damaged,
+ "vin": vin,
+ },
+ vehicle_create_params.VehicleCreateParams,
+ ),
+ options=make_request_options(
+ extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+ ),
+ cast_to=VehicleDetail,
+ )
+
+ async def retrieve(
+ self,
+ vehicle_id: str,
+ *,
+ dealer_id: str,
+ include_deleted: bool | Omit = omit,
+ # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+ # The extra values given here take precedence over values defined on the client or passed to this method.
+ extra_headers: Headers | None = None,
+ extra_query: Query | None = None,
+ extra_body: Body | None = None,
+ timeout: float | httpx.Timeout | None | NotGiven = not_given,
+ ) -> VehicleDetail:
+ """
+ Get Vehicle
+
+ Args:
+ include_deleted: If true, the detail of a soft-deleted vehicle is returned. Default false —
+ soft-deleted rows return 404 to keep behaviour consistent with the list
+ endpoint.
+
+ extra_headers: Send extra headers
+
+ extra_query: Add additional query parameters to the request
+
+ extra_body: Add additional JSON properties to the request
+
+ timeout: Override the client-level default timeout for this request, in seconds
+ """
+ if not dealer_id:
+ raise ValueError(f"Expected a non-empty value for `dealer_id` but received {dealer_id!r}")
+ if not vehicle_id:
+ raise ValueError(f"Expected a non-empty value for `vehicle_id` but received {vehicle_id!r}")
+ return await self._get(
+ path_template("/v1/dealers/{dealer_id}/vehicles/{vehicle_id}", dealer_id=dealer_id, vehicle_id=vehicle_id),
+ options=make_request_options(
+ extra_headers=extra_headers,
+ extra_query=extra_query,
+ extra_body=extra_body,
+ timeout=timeout,
+ query=await async_maybe_transform(
+ {"include_deleted": include_deleted}, vehicle_retrieve_params.VehicleRetrieveParams
+ ),
+ ),
+ cast_to=VehicleDetail,
+ )
+
+ async def update(
+ self,
+ vehicle_id: str,
+ *,
+ dealer_id: str,
+ alloy_wheel_size: Optional[int] | Omit = omit,
+ certified_km: Optional[int] | Omit = omit,
+ color: Optional[str] | Omit = omit,
+ cost_price_eur: Optional[float] | Omit = omit,
+ description: Optional[str] | Omit = omit,
+ extended_warranty_enabled: Optional[bool] | Omit = omit,
+ extended_warranty_months: Optional[int] | Omit = omit,
+ inspection_expiry_date: Union[str, date, None] | Omit = omit,
+ is_for_sale: Optional[bool] | Omit = omit,
+ is_visible: Optional[bool] | Omit = omit,
+ last_service_date: Union[str, date, None] | Omit = omit,
+ last_service_km: Optional[int] | Omit = omit,
+ last_service_notes: Optional[str] | Omit = omit,
+ notes: Optional[str] | Omit = omit,
+ previous_owner_count: Optional[int] | Omit = omit,
+ previous_ownership_transfer_date: Union[str, date, None] | Omit = omit,
+ registration_month: Optional[int] | Omit = omit,
+ road_tax_expiry_date: Union[str, date, None] | Omit = omit,
+ sale_price_eur: Optional[float] | Omit = omit,
+ vat_displayed: Optional[bool] | Omit = omit,
+ vehicle_damaged: Optional[bool] | Omit = omit,
+ idempotency_key: str | Omit = omit,
+ # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+ # The extra values given here take precedence over values defined on the client or passed to this method.
+ extra_headers: Headers | None = None,
+ extra_query: Query | None = None,
+ extra_body: Body | None = None,
+ timeout: float | httpx.Timeout | None | NotGiven = not_given,
+ ) -> VehicleDetail:
+ """
+ Partial update of a vehicle.
+
+ Splits the inbound body across the two physical tables (`azlease_usatoauto` and
+ `azlease_usatoin`) and emits at most one UPDATE per table inside a single
+ transaction. Fields not present in the body are not touched.
+
+ Args:
+ extra_headers: Send extra headers
+
+ extra_query: Add additional query parameters to the request
+
+ extra_body: Add additional JSON properties to the request
+
+ timeout: Override the client-level default timeout for this request, in seconds
+ """
+ if not dealer_id:
+ raise ValueError(f"Expected a non-empty value for `dealer_id` but received {dealer_id!r}")
+ if not vehicle_id:
+ raise ValueError(f"Expected a non-empty value for `vehicle_id` but received {vehicle_id!r}")
+ extra_headers = {**strip_not_given({"Idempotency-Key": idempotency_key}), **(extra_headers or {})}
+ return await self._patch(
+ path_template("/v1/dealers/{dealer_id}/vehicles/{vehicle_id}", dealer_id=dealer_id, vehicle_id=vehicle_id),
+ body=await async_maybe_transform(
+ {
+ "alloy_wheel_size": alloy_wheel_size,
+ "certified_km": certified_km,
+ "color": color,
+ "cost_price_eur": cost_price_eur,
+ "description": description,
+ "extended_warranty_enabled": extended_warranty_enabled,
+ "extended_warranty_months": extended_warranty_months,
+ "inspection_expiry_date": inspection_expiry_date,
+ "is_for_sale": is_for_sale,
+ "is_visible": is_visible,
+ "last_service_date": last_service_date,
+ "last_service_km": last_service_km,
+ "last_service_notes": last_service_notes,
+ "notes": notes,
+ "previous_owner_count": previous_owner_count,
+ "previous_ownership_transfer_date": previous_ownership_transfer_date,
+ "registration_month": registration_month,
+ "road_tax_expiry_date": road_tax_expiry_date,
+ "sale_price_eur": sale_price_eur,
+ "vat_displayed": vat_displayed,
+ "vehicle_damaged": vehicle_damaged,
+ },
+ vehicle_update_params.VehicleUpdateParams,
+ ),
+ options=make_request_options(
+ extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+ ),
+ cast_to=VehicleDetail,
+ )
+
+ async def list(
+ self,
+ dealer_id: str,
+ *,
+ cursor: Optional[str] | Omit = omit,
+ include_deleted: bool | Omit = omit,
+ is_for_sale: Optional[bool] | Omit = omit,
+ is_visible: Optional[bool] | Omit = omit,
+ limit: int | Omit = omit,
+ # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+ # The extra values given here take precedence over values defined on the client or passed to this method.
+ extra_headers: Headers | None = None,
+ extra_query: Query | None = None,
+ extra_body: Body | None = None,
+ timeout: float | httpx.Timeout | None | NotGiven = not_given,
+ ) -> VehicleList:
+ """
+ List vehicles in a dealer's stock owned by the calling partner.
+
+ Cursor pagination is opaque base64url over the last vehicle UUID. Default sort
+ is `i.data_inserimento ASC` so freshly provisioned vehicles surface at the tail.
+ Soft-deleted rows are excluded unless `include_deleted=true` is set explicitly —
+ this preserves the soft-delete semantic across the API contract.
+
+ Args:
+ include_deleted: If true, soft-deleted rows (`venduto_il` populated) are also returned. Default
+ false — listings hide soft-deleted vehicles.
+
+ is_for_sale: Filter on the sale flag.
+
+ is_visible: Filter on the visibility flag.
+
+ extra_headers: Send extra headers
+
+ extra_query: Add additional query parameters to the request
+
+ extra_body: Add additional JSON properties to the request
+
+ timeout: Override the client-level default timeout for this request, in seconds
+ """
+ if not dealer_id:
+ raise ValueError(f"Expected a non-empty value for `dealer_id` but received {dealer_id!r}")
+ return await self._get(
+ path_template("/v1/dealers/{dealer_id}/vehicles", dealer_id=dealer_id),
+ options=make_request_options(
+ extra_headers=extra_headers,
+ extra_query=extra_query,
+ extra_body=extra_body,
+ timeout=timeout,
+ query=await async_maybe_transform(
+ {
+ "cursor": cursor,
+ "include_deleted": include_deleted,
+ "is_for_sale": is_for_sale,
+ "is_visible": is_visible,
+ "limit": limit,
+ },
+ vehicle_list_params.VehicleListParams,
+ ),
+ ),
+ cast_to=VehicleList,
+ )
+
+ async def delete(
+ self,
+ vehicle_id: str,
+ *,
+ dealer_id: str,
+ # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+ # The extra values given here take precedence over values defined on the client or passed to this method.
+ extra_headers: Headers | None = None,
+ extra_query: Query | None = None,
+ extra_body: Body | None = None,
+ timeout: float | httpx.Timeout | None | NotGiven = not_given,
+ ) -> None:
+ """
+ Withdraw a vehicle from sale without deleting the row.
+
+ Sets `azlease_usatoin.visibile = FALSE` and stamps `venduto_il = now()`. The
+ plate becomes reusable on the network the moment this returns (the
+ active-uniqueness check excludes rows where `visibile = FALSE` OR
+ `venduto_il IS NOT NULL`).
+
+ Soft-delete is the canonical "remove this vehicle from sale" surface. The
+ AI-citation consumers (MCP `_tool_search_vehicles`, Custom GPT
+ `search_vehicles_network`, NLWeb `/ask`) each filter their own queries on
+ `i.visibile = TRUE AND i.venduto_il IS NULL` — the shared `v_apimax_listing`
+ view itself does not impose that filter, every consumer adds it. The result on
+ the partner side is the same: a soft-deleted vehicle disappears from every AI
+ surface within the next index cycle.
+
+ Returns `409 vehicle_already_deleted` if the row is already soft- deleted — same
+ idempotency pattern as the dealers DELETE endpoint.
+
+ Args:
+ extra_headers: Send extra headers
+
+ extra_query: Add additional query parameters to the request
+
+ extra_body: Add additional JSON properties to the request
+
+ timeout: Override the client-level default timeout for this request, in seconds
+ """
+ if not dealer_id:
+ raise ValueError(f"Expected a non-empty value for `dealer_id` but received {dealer_id!r}")
+ if not vehicle_id:
+ raise ValueError(f"Expected a non-empty value for `vehicle_id` but received {vehicle_id!r}")
+ extra_headers = {"Accept": "*/*", **(extra_headers or {})}
+ return await self._delete(
+ path_template("/v1/dealers/{dealer_id}/vehicles/{vehicle_id}", dealer_id=dealer_id, vehicle_id=vehicle_id),
+ options=make_request_options(
+ extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+ ),
+ cast_to=NoneType,
+ )
+
+ async def bulk(
+ self,
+ dealer_id: str,
+ *,
+ vehicles: Iterable[vehicle_bulk_params.Vehicle],
+ idempotency_key: str | Omit = omit,
+ # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+ # The extra values given here take precedence over values defined on the client or passed to this method.
+ extra_headers: Headers | None = None,
+ extra_query: Query | None = None,
+ extra_body: Body | None = None,
+ timeout: float | httpx.Timeout | None | NotGiven = not_given,
+ ) -> BulkCreateVehiclesResponse:
+ """
+ Provision up to `BULK_MAX_ROWS` vehicles in a single synchronous call.
+
+ Each row is processed inside its own `SAVEPOINT` so a failure on row N
+ (validation, plate conflict, motornet not in catalogue, race) is isolated — the
+ SAVEPOINT rolls back, the per-row outcome is collected with a structured error
+ code, and the loop continues with row N+1.
+
+ Successful rows accumulate in the outer transaction and are committed together
+ at the end of the request. Failed rows leave no trace in the database.
+
+ Returns `207 Multi-Status`. The response carries:
+
+ - `total`, `succeeded`, `failed` — aggregate counters for quick branch logic on
+ the partner side.
+ - `results` — array of per-row outcomes, indexed by the position in the request
+ `vehicles[]` array. Successful rows include the full `VehicleDetail`; failed
+ rows include `error_code` + `error_message` keyed to the same codes as the
+ single-POST surface so the partner reuses one error handler for both paths.
+
+ For imports larger than `BULK_MAX_ROWS` (currently 100), the partner is expected
+ to chunk the array client-side. A 5 000-vehicle initial migration is 50 calls;
+ the partner controls concurrency.
+
+ Args:
+ vehicles: Array of vehicles to create. Between 1 and 100 rows per call. For larger
+ imports, the partner is expected to chunk client-side (e.g. 50 calls of 100 rows
+ each for a 5 000-vehicle migration).
+
+ extra_headers: Send extra headers
+
+ extra_query: Add additional query parameters to the request
+
+ extra_body: Add additional JSON properties to the request
+
+ timeout: Override the client-level default timeout for this request, in seconds
+ """
+ if not dealer_id:
+ raise ValueError(f"Expected a non-empty value for `dealer_id` but received {dealer_id!r}")
+ extra_headers = {**strip_not_given({"Idempotency-Key": idempotency_key}), **(extra_headers or {})}
+ return await self._post(
+ path_template("/v1/dealers/{dealer_id}/vehicles/bulk", dealer_id=dealer_id),
+ body=await async_maybe_transform({"vehicles": vehicles}, vehicle_bulk_params.VehicleBulkParams),
+ options=make_request_options(
+ extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+ ),
+ cast_to=BulkCreateVehiclesResponse,
+ )
+
+
+class VehiclesResourceWithRawResponse:
+ def __init__(self, vehicles: VehiclesResource) -> None:
+ self._vehicles = vehicles
+
+ self.create = to_raw_response_wrapper(
+ vehicles.create,
+ )
+ self.retrieve = to_raw_response_wrapper(
+ vehicles.retrieve,
+ )
+ self.update = to_raw_response_wrapper(
+ vehicles.update,
+ )
+ self.list = to_raw_response_wrapper(
+ vehicles.list,
+ )
+ self.delete = to_raw_response_wrapper(
+ vehicles.delete,
+ )
+ self.bulk = to_raw_response_wrapper(
+ vehicles.bulk,
+ )
+
+
+class AsyncVehiclesResourceWithRawResponse:
+ def __init__(self, vehicles: AsyncVehiclesResource) -> None:
+ self._vehicles = vehicles
+
+ self.create = async_to_raw_response_wrapper(
+ vehicles.create,
+ )
+ self.retrieve = async_to_raw_response_wrapper(
+ vehicles.retrieve,
+ )
+ self.update = async_to_raw_response_wrapper(
+ vehicles.update,
+ )
+ self.list = async_to_raw_response_wrapper(
+ vehicles.list,
+ )
+ self.delete = async_to_raw_response_wrapper(
+ vehicles.delete,
+ )
+ self.bulk = async_to_raw_response_wrapper(
+ vehicles.bulk,
+ )
+
+
+class VehiclesResourceWithStreamingResponse:
+ def __init__(self, vehicles: VehiclesResource) -> None:
+ self._vehicles = vehicles
+
+ self.create = to_streamed_response_wrapper(
+ vehicles.create,
+ )
+ self.retrieve = to_streamed_response_wrapper(
+ vehicles.retrieve,
+ )
+ self.update = to_streamed_response_wrapper(
+ vehicles.update,
+ )
+ self.list = to_streamed_response_wrapper(
+ vehicles.list,
+ )
+ self.delete = to_streamed_response_wrapper(
+ vehicles.delete,
+ )
+ self.bulk = to_streamed_response_wrapper(
+ vehicles.bulk,
+ )
+
+
+class AsyncVehiclesResourceWithStreamingResponse:
+ def __init__(self, vehicles: AsyncVehiclesResource) -> None:
+ self._vehicles = vehicles
+
+ self.create = async_to_streamed_response_wrapper(
+ vehicles.create,
+ )
+ self.retrieve = async_to_streamed_response_wrapper(
+ vehicles.retrieve,
+ )
+ self.update = async_to_streamed_response_wrapper(
+ vehicles.update,
+ )
+ self.list = async_to_streamed_response_wrapper(
+ vehicles.list,
+ )
+ self.delete = async_to_streamed_response_wrapper(
+ vehicles.delete,
+ )
+ self.bulk = async_to_streamed_response_wrapper(
+ vehicles.bulk,
+ )
diff --git a/src/partnermax/resources/keys.py b/src/partnermax/resources/keys.py
index 8f9e42b..b340bd6 100644
--- a/src/partnermax/resources/keys.py
+++ b/src/partnermax/resources/keys.py
@@ -60,11 +60,11 @@ def list(
extra_body: Body | None = None,
timeout: float | httpx.Timeout | None | NotGiven = not_given,
) -> KeyListResponse:
- """Returns metadata for all active keys belonging to the calling partner.
+ """
+ List metadata for keys owned by the calling partner.
- Key
- material is never returned — only the prefix (first 8 characters) for safe
- logging and identification.
+ Returns both active and revoked keys — partners can audit revoked keys via the
+ `is_active` flag. Key material is never returned.
"""
return self._get(
"/v1/keys",
@@ -78,7 +78,7 @@ def issue(
self,
*,
label: str,
- expires_at: Union[str, datetime] | Omit = omit,
+ expires_at: Union[str, datetime, None] | Omit = omit,
idempotency_key: str | Omit = omit,
# Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
# The extra values given here take precedence over values defined on the client or passed to this method.
@@ -87,14 +87,14 @@ def issue(
extra_body: Body | None = None,
timeout: float | httpx.Timeout | None | NotGiven = not_given,
) -> KeyIssueResponse:
- """Creates a new API key for the calling partner.
+ """Issue a new API key.
+
+ Plaintext returned exactly once.
- The key material is returned in
- plaintext in the response and is never retrievable again — store it securely on
- first receipt. Must be called with an existing API key that has the
- `can_issue_keys` capability (the initial key issued by DealerMAX support has
- this capability by default; rotated keys inherit it unless explicitly scoped
- down).
+ Capability gate: caller's key must hold `can_issue_keys`. The bootstrap key
+ handed to a partner by DealerMAX support carries this capability; keys minted
+ here inherit the _same_ capabilities as the caller, so partners can never
+ accidentally widen their own scope.
Args:
label: Human-readable identifier for this key, used for safe logging.
@@ -136,11 +136,9 @@ def revoke(
extra_body: Body | None = None,
timeout: float | httpx.Timeout | None | NotGiven = not_given,
) -> None:
- """Immediately invalidates the specified key.
+ """Revoke a key.
- Any in-flight requests using this key
- will continue until completion; subsequent requests will receive 401
- invalid_api_key. Revocation is logged in the audit trail.
+ Cannot revoke the key currently authenticating the request.
Args:
extra_headers: Send extra headers
@@ -198,11 +196,11 @@ async def list(
extra_body: Body | None = None,
timeout: float | httpx.Timeout | None | NotGiven = not_given,
) -> KeyListResponse:
- """Returns metadata for all active keys belonging to the calling partner.
+ """
+ List metadata for keys owned by the calling partner.
- Key
- material is never returned — only the prefix (first 8 characters) for safe
- logging and identification.
+ Returns both active and revoked keys — partners can audit revoked keys via the
+ `is_active` flag. Key material is never returned.
"""
return await self._get(
"/v1/keys",
@@ -216,7 +214,7 @@ async def issue(
self,
*,
label: str,
- expires_at: Union[str, datetime] | Omit = omit,
+ expires_at: Union[str, datetime, None] | Omit = omit,
idempotency_key: str | Omit = omit,
# Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
# The extra values given here take precedence over values defined on the client or passed to this method.
@@ -225,14 +223,14 @@ async def issue(
extra_body: Body | None = None,
timeout: float | httpx.Timeout | None | NotGiven = not_given,
) -> KeyIssueResponse:
- """Creates a new API key for the calling partner.
+ """Issue a new API key.
+
+ Plaintext returned exactly once.
- The key material is returned in
- plaintext in the response and is never retrievable again — store it securely on
- first receipt. Must be called with an existing API key that has the
- `can_issue_keys` capability (the initial key issued by DealerMAX support has
- this capability by default; rotated keys inherit it unless explicitly scoped
- down).
+ Capability gate: caller's key must hold `can_issue_keys`. The bootstrap key
+ handed to a partner by DealerMAX support carries this capability; keys minted
+ here inherit the _same_ capabilities as the caller, so partners can never
+ accidentally widen their own scope.
Args:
label: Human-readable identifier for this key, used for safe logging.
@@ -274,11 +272,9 @@ async def revoke(
extra_body: Body | None = None,
timeout: float | httpx.Timeout | None | NotGiven = not_given,
) -> None:
- """Immediately invalidates the specified key.
+ """Revoke a key.
- Any in-flight requests using this key
- will continue until completion; subsequent requests will receive 401
- invalid_api_key. Revocation is logged in the audit trail.
+ Cannot revoke the key currently authenticating the request.
Args:
extra_headers: Send extra headers
diff --git a/src/partnermax/types/dealer_create_params.py b/src/partnermax/types/dealer_create_params.py
index 6d77999..91e5534 100644
--- a/src/partnermax/types/dealer_create_params.py
+++ b/src/partnermax/types/dealer_create_params.py
@@ -11,32 +11,26 @@
class DealerCreateParams(TypedDict, total=False):
+ address: Required[str]
+
business_name: Required[str]
+ city: Required[str]
+
contact_email: Required[str]
+ contact_phone: Required[str]
+
postal_code: Required[str]
- """Italian 5-digit postal code."""
primary_domain: Required[str]
- """Root domain of the dealer's public website."""
province_code: Required[str]
- """Italian two-letter province code, e.g., `MI`, `RM`, `TO`."""
vat_number: Required[str]
- """Italian VAT number, 11 digits prefixed with `IT`."""
activate: bool
- """
- If false, dealer is created in inactive state and does not appear in AI surfaces
- until activated.
- """
-
- contact_phone: str
- """E.164 format recommended."""
metadata: Dict[str, str]
- """Free-form partner-supplied key-value pairs, max 16 keys, values max 500 chars."""
idempotency_key: Annotated[str, PropertyInfo(alias="Idempotency-Key")]
diff --git a/src/partnermax/types/dealer_detail.py b/src/partnermax/types/dealer_detail.py
index 3831c99..b80fcb9 100644
--- a/src/partnermax/types/dealer_detail.py
+++ b/src/partnermax/types/dealer_detail.py
@@ -1,18 +1,20 @@
# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
from typing import Dict, Optional
+from datetime import datetime
+from typing_extensions import Literal
from .._models import BaseModel
-from .dealer_summary import DealerSummary
-from .dealers.nlt_settings import NltSettings
-__all__ = ["DealerDetail", "DealerDetailIndexedInSurfaces"]
+__all__ = ["DealerDetail", "IndexedInSurfaces"]
-class DealerDetailIndexedInSurfaces(BaseModel):
- """Live indexing state for each cross-network AI surface.
+class IndexedInSurfaces(BaseModel):
+ """Per-surface AI indexing state.
- Values may be `false` immediately after provisioning; reaches `true` within five minutes.
+ All values may be ``false`` immediately
+ after provisioning; reconciliation by ``azurenet-engine`` flips them within
+ five minutes.
"""
custom_gpt: Optional[bool] = None
@@ -24,29 +26,46 @@ class DealerDetailIndexedInSurfaces(BaseModel):
nlweb: Optional[bool] = None
-class DealerDetail(DealerSummary):
- contact_email: Optional[str] = None
+class DealerDetail(BaseModel):
+ """Full dealer payload used by single-resource and write endpoints."""
- contact_phone: Optional[str] = None
+ address: str
- indexed_in_surfaces: Optional[DealerDetailIndexedInSurfaces] = None
- """Live indexing state for each cross-network AI surface.
+ business_name: str
- Values may be `false` immediately after provisioning; reaches `true` within five
- minutes.
- """
+ city: str
- metadata: Optional[Dict[str, str]] = None
+ contact_email: str
+
+ created_at: datetime
+
+ dealer_id: str
+
+ nlt_enabled: bool
+
+ partner_id: str
+
+ postal_code: str
- nlt_settings: Optional[NltSettings] = None
- """Dealer-level NLT economics + image rendering preferences.
+ primary_domain: str
- VAT treatment is NOT a dealer-level field — it is a property of the offer (see
- `NltOfferSummary.vat_treatment`).
+ province_code: str
+
+ status: Literal["active", "inactive", "deleted"]
+
+ vat_number: str
+
+ contact_phone: Optional[str] = None
+
+ indexed_in_surfaces: Optional[IndexedInSurfaces] = None
+ """Per-surface AI indexing state.
+
+ All values may be `false` immediately after provisioning; reconciliation by
+ `azurenet-engine` flips them within five minutes.
"""
- partner_id: Optional[str] = None
+ last_active_at: Optional[datetime] = None
- postal_code: Optional[str] = None
+ metadata: Optional[Dict[str, str]] = None
- vat_number: Optional[str] = None
+ nlt_settings: Optional[Dict[str, object]] = None
diff --git a/src/partnermax/types/dealer_list_params.py b/src/partnermax/types/dealer_list_params.py
index aa81405..f54d641 100644
--- a/src/partnermax/types/dealer_list_params.py
+++ b/src/partnermax/types/dealer_list_params.py
@@ -2,17 +2,15 @@
from __future__ import annotations
+from typing import Optional
from typing_extensions import Literal, TypedDict
__all__ = ["DealerListParams"]
class DealerListParams(TypedDict, total=False):
- cursor: str
- """Opaque pagination cursor from a previous response's `next_cursor`."""
+ cursor: Optional[str]
limit: int
- """Maximum number of items to return."""
status: Literal["active", "inactive", "all"]
- """Filter by dealer status."""
diff --git a/src/partnermax/types/dealer_list_response.py b/src/partnermax/types/dealer_list_response.py
index bf17203..436dc2e 100644
--- a/src/partnermax/types/dealer_list_response.py
+++ b/src/partnermax/types/dealer_list_response.py
@@ -9,9 +9,10 @@
class DealerListResponse(BaseModel):
+ """Response envelope for ``GET /v1/dealers``."""
+
data: List[DealerSummary]
has_more: bool
next_cursor: Optional[str] = None
- """Pass as `cursor` to retrieve next page; null when no more pages."""
diff --git a/src/partnermax/types/dealer_summary.py b/src/partnermax/types/dealer_summary.py
index e65c1a1..69ecd6b 100644
--- a/src/partnermax/types/dealer_summary.py
+++ b/src/partnermax/types/dealer_summary.py
@@ -10,6 +10,8 @@
class DealerSummary(BaseModel):
+ """Compact dealer payload used by list endpoints."""
+
business_name: str
created_at: datetime
diff --git a/src/partnermax/types/dealer_update_params.py b/src/partnermax/types/dealer_update_params.py
index 885f7e1..251f5cf 100644
--- a/src/partnermax/types/dealer_update_params.py
+++ b/src/partnermax/types/dealer_update_params.py
@@ -2,7 +2,7 @@
from __future__ import annotations
-from typing import Dict
+from typing import Dict, Optional
from typing_extensions import Literal, Annotated, TypedDict
from .._utils import PropertyInfo
@@ -11,22 +11,22 @@
class DealerUpdateParams(TypedDict, total=False):
- business_name: str
+ address: Optional[str]
- contact_email: str
+ business_name: Optional[str]
- contact_phone: str
+ city: Optional[str]
- metadata: Dict[str, str]
+ contact_email: Optional[str]
- postal_code: str
+ contact_phone: Optional[str]
- province_code: str
+ metadata: Optional[Dict[str, str]]
- status: Literal["active", "inactive"]
- """Toggle activation.
+ postal_code: Optional[str]
- Inactive dealers are removed from AI surfaces within 5 minutes.
- """
+ province_code: Optional[str]
+
+ status: Optional[Literal["active", "inactive"]]
idempotency_key: Annotated[str, PropertyInfo(alias="Idempotency-Key")]
diff --git a/src/partnermax/types/dealers/__init__.py b/src/partnermax/types/dealers/__init__.py
index d04cf6f..f97da52 100644
--- a/src/partnermax/types/dealers/__init__.py
+++ b/src/partnermax/types/dealers/__init__.py
@@ -3,6 +3,16 @@
from __future__ import annotations
from .nlt_settings import NltSettings as NltSettings
+from .vehicle_list import VehicleList as VehicleList
+from .vehicle_detail import VehicleDetail as VehicleDetail
+from .vehicle_summary import VehicleSummary as VehicleSummary
+from .bulk_row_outcome import BulkRowOutcome as BulkRowOutcome
from .down_payment_tiers import DownPaymentTiers as DownPaymentTiers
+from .vehicle_bulk_params import VehicleBulkParams as VehicleBulkParams
+from .vehicle_list_params import VehicleListParams as VehicleListParams
+from .vehicle_create_params import VehicleCreateParams as VehicleCreateParams
+from .vehicle_update_params import VehicleUpdateParams as VehicleUpdateParams
+from .vehicle_retrieve_params import VehicleRetrieveParams as VehicleRetrieveParams
from .down_payment_tiers_param import DownPaymentTiersParam as DownPaymentTiersParam
from .nlt_setting_update_params import NltSettingUpdateParams as NltSettingUpdateParams
+from .bulk_create_vehicles_response import BulkCreateVehiclesResponse as BulkCreateVehiclesResponse
diff --git a/src/partnermax/types/dealers/bulk_create_vehicles_response.py b/src/partnermax/types/dealers/bulk_create_vehicles_response.py
new file mode 100644
index 0000000..e710615
--- /dev/null
+++ b/src/partnermax/types/dealers/bulk_create_vehicles_response.py
@@ -0,0 +1,27 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from typing import List
+
+from ..._models import BaseModel
+from .bulk_row_outcome import BulkRowOutcome
+
+__all__ = ["BulkCreateVehiclesResponse"]
+
+
+class BulkCreateVehiclesResponse(BaseModel):
+ """Response of ``POST /v1/dealers/{dealer_id}/vehicles/bulk``.
+
+ HTTP status is ``207 Multi-Status`` (rather than 201) to make
+ partial success explicit at the protocol level. The aggregate
+ counters at the top let a partner short-circuit when every row
+ succeeded; the ``results`` array carries per-row detail when not.
+ """
+
+ failed: int
+
+ results: List[BulkRowOutcome]
+
+ succeeded: int
+
+ total: int
+ """Number of rows in the request body."""
diff --git a/src/partnermax/types/dealers/bulk_row_outcome.py b/src/partnermax/types/dealers/bulk_row_outcome.py
new file mode 100644
index 0000000..e3b6cb7
--- /dev/null
+++ b/src/partnermax/types/dealers/bulk_row_outcome.py
@@ -0,0 +1,47 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from typing import Optional
+from typing_extensions import Literal
+
+from ..._models import BaseModel
+from .vehicle_detail import VehicleDetail
+
+__all__ = ["BulkRowOutcome"]
+
+
+class BulkRowOutcome(BaseModel):
+ """Per-row result inside ``BulkCreateVehiclesResponse``.
+
+ On success ``vehicle`` is populated (full ``VehicleDetail``) and
+ ``error_code``/``error_message`` are null. On failure ``vehicle`` is
+ null and ``error_code``/``error_message`` carry the same code that
+ the corresponding single-create POST would have raised
+ (e.g. ``motornet_code_not_in_catalogue``, ``vehicle_plate_already_registered``,
+ ``validation_error``).
+ """
+
+ row_index: int
+ """Zero-based index of the row in the request `vehicles[]` array.
+
+ Stable across retries — the partner can correlate failures back to its own batch
+ by this index.
+ """
+
+ status: Literal["succeeded", "failed"]
+
+ error_code: Optional[str] = None
+
+ error_message: Optional[str] = None
+
+ vehicle: Optional[VehicleDetail] = None
+ """Full vehicle resource.
+
+ Returned by `GET /v1/dealers/{id}/vehicles/{id}`,
+ `POST /v1/dealers/{id}/vehicles`, and `PATCH /v1/dealers/{id}/vehicles/{id}`.
+
+ `technical_details` carries the flat Motornet specs dict (Italian column names
+ as keys: `cilindrata`, `kw`, `hp`, `lunghezza`, `consumo_medio`,
+ `emissioni_co2`, etc.). Same shape conventions as `NltOfferDetail`
+ (`feedback_partnermax_field_naming_us_english`: field names are English
+ snake_case, raw catalogue values stay verbatim).
+ """
diff --git a/src/partnermax/types/dealers/down_payment_tiers.py b/src/partnermax/types/dealers/down_payment_tiers.py
index 7cf52ce..eb528d8 100644
--- a/src/partnermax/types/dealers/down_payment_tiers.py
+++ b/src/partnermax/types/dealers/down_payment_tiers.py
@@ -6,54 +6,81 @@
class High(BaseModel):
- """
- One down-payment tier — `final_eur = listino_imponibile * (percent_of_list / 100) + fixed_eur`. Persisted in apimax shape `{"pct": <0..1>, "eur": }` on `dealer_public.nlt_anticipi_config`.
+ """One down-payment tier — percent of list price + flat EUR.
+
+ apimax: `dealer_public.nlt_anticipi_config` is a JSONB list of three
+ `{"pct": <0..1>, "eur": }` entries. The final EUR applied to
+ a deal is `listino_imponibile * pct + eur` (see
+ `apimax/app/services/nlt/calculator.py::calcola_anticipo_eur`).
+
+ Partnermax API exposes `percent_of_list` as a 0–100 number
+ (UI-friendly: write `12.5`, not `0.125`); the router persists
+ `pct = percent_of_list / 100` to stay byte-compatible with the
+ DealerMAX UI calculator.
"""
fixed_eur: int
"""Flat EUR component added on top of the percentage (e.g.
- promo `0% + 500€ fissi`). Whole euros only.
+ promo `0% + 500 EUR fissi`). Whole euros only.
"""
percent_of_list: float
- """Percent of the IVA-excluded list price applied as down payment for this tier.
+ """Percentage of the IVA-excluded list price applied as down payment for this tier.
Range 0–100. Typical defaults: 0 (low), 12.5 (medium), 25 (high).
"""
class Low(BaseModel):
- """
- One down-payment tier — `final_eur = listino_imponibile * (percent_of_list / 100) + fixed_eur`. Persisted in apimax shape `{"pct": <0..1>, "eur": }` on `dealer_public.nlt_anticipi_config`.
+ """One down-payment tier — percent of list price + flat EUR.
+
+ apimax: `dealer_public.nlt_anticipi_config` is a JSONB list of three
+ `{"pct": <0..1>, "eur": }` entries. The final EUR applied to
+ a deal is `listino_imponibile * pct + eur` (see
+ `apimax/app/services/nlt/calculator.py::calcola_anticipo_eur`).
+
+ Partnermax API exposes `percent_of_list` as a 0–100 number
+ (UI-friendly: write `12.5`, not `0.125`); the router persists
+ `pct = percent_of_list / 100` to stay byte-compatible with the
+ DealerMAX UI calculator.
"""
fixed_eur: int
"""Flat EUR component added on top of the percentage (e.g.
- promo `0% + 500€ fissi`). Whole euros only.
+ promo `0% + 500 EUR fissi`). Whole euros only.
"""
percent_of_list: float
- """Percent of the IVA-excluded list price applied as down payment for this tier.
+ """Percentage of the IVA-excluded list price applied as down payment for this tier.
Range 0–100. Typical defaults: 0 (low), 12.5 (medium), 25 (high).
"""
class Medium(BaseModel):
- """
- One down-payment tier — `final_eur = listino_imponibile * (percent_of_list / 100) + fixed_eur`. Persisted in apimax shape `{"pct": <0..1>, "eur": }` on `dealer_public.nlt_anticipi_config`.
+ """One down-payment tier — percent of list price + flat EUR.
+
+ apimax: `dealer_public.nlt_anticipi_config` is a JSONB list of three
+ `{"pct": <0..1>, "eur": }` entries. The final EUR applied to
+ a deal is `listino_imponibile * pct + eur` (see
+ `apimax/app/services/nlt/calculator.py::calcola_anticipo_eur`).
+
+ Partnermax API exposes `percent_of_list` as a 0–100 number
+ (UI-friendly: write `12.5`, not `0.125`); the router persists
+ `pct = percent_of_list / 100` to stay byte-compatible with the
+ DealerMAX UI calculator.
"""
fixed_eur: int
"""Flat EUR component added on top of the percentage (e.g.
- promo `0% + 500€ fissi`). Whole euros only.
+ promo `0% + 500 EUR fissi`). Whole euros only.
"""
percent_of_list: float
- """Percent of the IVA-excluded list price applied as down payment for this tier.
+ """Percentage of the IVA-excluded list price applied as down payment for this tier.
Range 0–100. Typical defaults: 0 (low), 12.5 (medium), 25 (high).
"""
@@ -62,29 +89,49 @@ class Medium(BaseModel):
class DownPaymentTiers(BaseModel):
"""Three down-payment scenarios (basso / medio / alto).
- Each tier carries `{percent_of_list (0–100), fixed_eur (≥0)}`. No strict-ascending check — the final EUR per tier is offer-dependent (`listino_imponibile * pct + eur`).
+ No strict-ascending validation: the final EUR amount depends on
+ the offer's list price (`tier.percent_of_list / 100 *
+ listino_imponibile + tier.fixed_eur`), so a tier that looks
+ larger by % can produce a smaller EUR on cheap vehicles. Label
+ semantics (low/medium/high) are advisory — apimax/DealerMAX UI
+ treats the 3 positions as opaque slots ordered by intent.
"""
high: High
- """
- One down-payment tier —
- `final_eur = listino_imponibile * (percent_of_list / 100) + fixed_eur`.
- Persisted in apimax shape `{"pct": <0..1>, "eur": }` on
- `dealer_public.nlt_anticipi_config`.
+ """One down-payment tier — percent of list price + flat EUR.
+
+ apimax: `dealer_public.nlt_anticipi_config` is a JSONB list of three
+ `{"pct": <0..1>, "eur": }` entries. The final EUR applied to a deal is
+ `listino_imponibile * pct + eur` (see
+ `apimax/app/services/nlt/calculator.py::calcola_anticipo_eur`).
+
+ Partnermax API exposes `percent_of_list` as a 0–100 number (UI-friendly: write
+ `12.5`, not `0.125`); the router persists `pct = percent_of_list / 100` to stay
+ byte-compatible with the DealerMAX UI calculator.
"""
low: Low
- """
- One down-payment tier —
- `final_eur = listino_imponibile * (percent_of_list / 100) + fixed_eur`.
- Persisted in apimax shape `{"pct": <0..1>, "eur": }` on
- `dealer_public.nlt_anticipi_config`.
+ """One down-payment tier — percent of list price + flat EUR.
+
+ apimax: `dealer_public.nlt_anticipi_config` is a JSONB list of three
+ `{"pct": <0..1>, "eur": }` entries. The final EUR applied to a deal is
+ `listino_imponibile * pct + eur` (see
+ `apimax/app/services/nlt/calculator.py::calcola_anticipo_eur`).
+
+ Partnermax API exposes `percent_of_list` as a 0–100 number (UI-friendly: write
+ `12.5`, not `0.125`); the router persists `pct = percent_of_list / 100` to stay
+ byte-compatible with the DealerMAX UI calculator.
"""
medium: Medium
- """
- One down-payment tier —
- `final_eur = listino_imponibile * (percent_of_list / 100) + fixed_eur`.
- Persisted in apimax shape `{"pct": <0..1>, "eur": }` on
- `dealer_public.nlt_anticipi_config`.
+ """One down-payment tier — percent of list price + flat EUR.
+
+ apimax: `dealer_public.nlt_anticipi_config` is a JSONB list of three
+ `{"pct": <0..1>, "eur": }` entries. The final EUR applied to a deal is
+ `listino_imponibile * pct + eur` (see
+ `apimax/app/services/nlt/calculator.py::calcola_anticipo_eur`).
+
+ Partnermax API exposes `percent_of_list` as a 0–100 number (UI-friendly: write
+ `12.5`, not `0.125`); the router persists `pct = percent_of_list / 100` to stay
+ byte-compatible with the DealerMAX UI calculator.
"""
diff --git a/src/partnermax/types/dealers/down_payment_tiers_param.py b/src/partnermax/types/dealers/down_payment_tiers_param.py
index 9024da5..6c0d5fe 100644
--- a/src/partnermax/types/dealers/down_payment_tiers_param.py
+++ b/src/partnermax/types/dealers/down_payment_tiers_param.py
@@ -8,54 +8,81 @@
class High(TypedDict, total=False):
- """
- One down-payment tier — `final_eur = listino_imponibile * (percent_of_list / 100) + fixed_eur`. Persisted in apimax shape `{"pct": <0..1>, "eur": }` on `dealer_public.nlt_anticipi_config`.
+ """One down-payment tier — percent of list price + flat EUR.
+
+ apimax: `dealer_public.nlt_anticipi_config` is a JSONB list of three
+ `{"pct": <0..1>, "eur": }` entries. The final EUR applied to
+ a deal is `listino_imponibile * pct + eur` (see
+ `apimax/app/services/nlt/calculator.py::calcola_anticipo_eur`).
+
+ Partnermax API exposes `percent_of_list` as a 0–100 number
+ (UI-friendly: write `12.5`, not `0.125`); the router persists
+ `pct = percent_of_list / 100` to stay byte-compatible with the
+ DealerMAX UI calculator.
"""
fixed_eur: Required[int]
"""Flat EUR component added on top of the percentage (e.g.
- promo `0% + 500€ fissi`). Whole euros only.
+ promo `0% + 500 EUR fissi`). Whole euros only.
"""
percent_of_list: Required[float]
- """Percent of the IVA-excluded list price applied as down payment for this tier.
+ """Percentage of the IVA-excluded list price applied as down payment for this tier.
Range 0–100. Typical defaults: 0 (low), 12.5 (medium), 25 (high).
"""
class Low(TypedDict, total=False):
- """
- One down-payment tier — `final_eur = listino_imponibile * (percent_of_list / 100) + fixed_eur`. Persisted in apimax shape `{"pct": <0..1>, "eur": }` on `dealer_public.nlt_anticipi_config`.
+ """One down-payment tier — percent of list price + flat EUR.
+
+ apimax: `dealer_public.nlt_anticipi_config` is a JSONB list of three
+ `{"pct": <0..1>, "eur": }` entries. The final EUR applied to
+ a deal is `listino_imponibile * pct + eur` (see
+ `apimax/app/services/nlt/calculator.py::calcola_anticipo_eur`).
+
+ Partnermax API exposes `percent_of_list` as a 0–100 number
+ (UI-friendly: write `12.5`, not `0.125`); the router persists
+ `pct = percent_of_list / 100` to stay byte-compatible with the
+ DealerMAX UI calculator.
"""
fixed_eur: Required[int]
"""Flat EUR component added on top of the percentage (e.g.
- promo `0% + 500€ fissi`). Whole euros only.
+ promo `0% + 500 EUR fissi`). Whole euros only.
"""
percent_of_list: Required[float]
- """Percent of the IVA-excluded list price applied as down payment for this tier.
+ """Percentage of the IVA-excluded list price applied as down payment for this tier.
Range 0–100. Typical defaults: 0 (low), 12.5 (medium), 25 (high).
"""
class Medium(TypedDict, total=False):
- """
- One down-payment tier — `final_eur = listino_imponibile * (percent_of_list / 100) + fixed_eur`. Persisted in apimax shape `{"pct": <0..1>, "eur": }` on `dealer_public.nlt_anticipi_config`.
+ """One down-payment tier — percent of list price + flat EUR.
+
+ apimax: `dealer_public.nlt_anticipi_config` is a JSONB list of three
+ `{"pct": <0..1>, "eur": }` entries. The final EUR applied to
+ a deal is `listino_imponibile * pct + eur` (see
+ `apimax/app/services/nlt/calculator.py::calcola_anticipo_eur`).
+
+ Partnermax API exposes `percent_of_list` as a 0–100 number
+ (UI-friendly: write `12.5`, not `0.125`); the router persists
+ `pct = percent_of_list / 100` to stay byte-compatible with the
+ DealerMAX UI calculator.
"""
fixed_eur: Required[int]
"""Flat EUR component added on top of the percentage (e.g.
- promo `0% + 500€ fissi`). Whole euros only.
+ promo `0% + 500 EUR fissi`). Whole euros only.
"""
percent_of_list: Required[float]
- """Percent of the IVA-excluded list price applied as down payment for this tier.
+ """Percentage of the IVA-excluded list price applied as down payment for this tier.
Range 0–100. Typical defaults: 0 (low), 12.5 (medium), 25 (high).
"""
@@ -64,29 +91,49 @@ class Medium(TypedDict, total=False):
class DownPaymentTiersParam(TypedDict, total=False):
"""Three down-payment scenarios (basso / medio / alto).
- Each tier carries `{percent_of_list (0–100), fixed_eur (≥0)}`. No strict-ascending check — the final EUR per tier is offer-dependent (`listino_imponibile * pct + eur`).
+ No strict-ascending validation: the final EUR amount depends on
+ the offer's list price (`tier.percent_of_list / 100 *
+ listino_imponibile + tier.fixed_eur`), so a tier that looks
+ larger by % can produce a smaller EUR on cheap vehicles. Label
+ semantics (low/medium/high) are advisory — apimax/DealerMAX UI
+ treats the 3 positions as opaque slots ordered by intent.
"""
high: Required[High]
- """
- One down-payment tier —
- `final_eur = listino_imponibile * (percent_of_list / 100) + fixed_eur`.
- Persisted in apimax shape `{"pct": <0..1>, "eur": }` on
- `dealer_public.nlt_anticipi_config`.
+ """One down-payment tier — percent of list price + flat EUR.
+
+ apimax: `dealer_public.nlt_anticipi_config` is a JSONB list of three
+ `{"pct": <0..1>, "eur": }` entries. The final EUR applied to a deal is
+ `listino_imponibile * pct + eur` (see
+ `apimax/app/services/nlt/calculator.py::calcola_anticipo_eur`).
+
+ Partnermax API exposes `percent_of_list` as a 0–100 number (UI-friendly: write
+ `12.5`, not `0.125`); the router persists `pct = percent_of_list / 100` to stay
+ byte-compatible with the DealerMAX UI calculator.
"""
low: Required[Low]
- """
- One down-payment tier —
- `final_eur = listino_imponibile * (percent_of_list / 100) + fixed_eur`.
- Persisted in apimax shape `{"pct": <0..1>, "eur": }` on
- `dealer_public.nlt_anticipi_config`.
+ """One down-payment tier — percent of list price + flat EUR.
+
+ apimax: `dealer_public.nlt_anticipi_config` is a JSONB list of three
+ `{"pct": <0..1>, "eur": }` entries. The final EUR applied to a deal is
+ `listino_imponibile * pct + eur` (see
+ `apimax/app/services/nlt/calculator.py::calcola_anticipo_eur`).
+
+ Partnermax API exposes `percent_of_list` as a 0–100 number (UI-friendly: write
+ `12.5`, not `0.125`); the router persists `pct = percent_of_list / 100` to stay
+ byte-compatible with the DealerMAX UI calculator.
"""
medium: Required[Medium]
- """
- One down-payment tier —
- `final_eur = listino_imponibile * (percent_of_list / 100) + fixed_eur`.
- Persisted in apimax shape `{"pct": <0..1>, "eur": }` on
- `dealer_public.nlt_anticipi_config`.
+ """One down-payment tier — percent of list price + flat EUR.
+
+ apimax: `dealer_public.nlt_anticipi_config` is a JSONB list of three
+ `{"pct": <0..1>, "eur": }` entries. The final EUR applied to a deal is
+ `listino_imponibile * pct + eur` (see
+ `apimax/app/services/nlt/calculator.py::calcola_anticipo_eur`).
+
+ Partnermax API exposes `percent_of_list` as a 0–100 number (UI-friendly: write
+ `12.5`, not `0.125`); the router persists `pct = percent_of_list / 100` to stay
+ byte-compatible with the DealerMAX UI calculator.
"""
diff --git a/src/partnermax/types/dealers/nlt/nlt_offer_summary.py b/src/partnermax/types/dealers/nlt/nlt_offer_summary.py
index 0615904..2dc878a 100644
--- a/src/partnermax/types/dealers/nlt/nlt_offer_summary.py
+++ b/src/partnermax/types/dealers/nlt/nlt_offer_summary.py
@@ -9,62 +9,41 @@
class NltOfferSummary(BaseModel):
+ """Single row in the offers list. Pricing is dealer-aware.
+
+ Field names: American English snake_case. Values: Italian raw,
+ apimax-aligned (`fuel_type: "Benzina"`, `segment: "SUV piccoli"`).
+ No enum normalization — apimax labels are surfaced verbatim, exactly
+ as the detail endpoint does, so the partner client sees the same
+ string in both listing and detail.
+ """
+
brand: str
dealer_id: str
- """Dealer id (prefixed `dlr_`)."""
- duration_months: Literal[36, 48, 60]
- """Duration corresponding to the `monthly_canon_from_eur` quote."""
+ duration_months: int
- km_per_year_at_quote: Literal[10000, 15000, 20000, 25000, 30000, 40000]
- """Km/year corresponding to the `monthly_canon_from_eur` quote."""
+ km_per_year_at_quote: int
model: str
monthly_canon_from_eur: float
- """
- Lowest displayed monthly canon across all duration / km / down-payment
- combinations for this dealer.
- """
offer_id: str
- """Numeric `nlt_offerte.id_offerta` as string.
-
- Use as the path parameter for the detail endpoint.
- """
slug: str
- """Offer slug used in canonical URLs (`/noleggio-lungo-termine/{slug}`)."""
vat_treatment: Literal["private", "business"]
- """VAT treatment of this offer (not the dealer).
-
- `private` → `monthly_canon_from_eur` is VAT-inclusive (×1.22). `business` →
- VAT-exclusive. Sourced from `nlt_offerte.solo_privati`.
- """
canonical_url: Optional[str] = None
- """
- Consumer-facing URL on the dealer's public site:
- `https://{primary_domain}/noleggio-lungo-termine/{slug}`. Null when the dealer
- has no site row.
- """
fuel_type: Optional[str] = None
- """Raw Italian label from `nlt_offerte.alimentazione` (e.g.
-
- "Benzina", "Ibrido diesel"). Apimax-aligned, no enum normalization.
- """
has_promo: Optional[bool] = None
image_url: Optional[str] = None
segment: Optional[str] = None
- """Raw Italian label from `nlt_offerte.segmento` (e.g.
-
- "SUV piccoli", "Superiori"). Apimax-aligned, no enum normalization.
- """
trim: Optional[str] = None
diff --git a/src/partnermax/types/dealers/nlt/offer_list_params.py b/src/partnermax/types/dealers/nlt/offer_list_params.py
index e335d2b..765ada0 100644
--- a/src/partnermax/types/dealers/nlt/offer_list_params.py
+++ b/src/partnermax/types/dealers/nlt/offer_list_params.py
@@ -2,36 +2,25 @@
from __future__ import annotations
-from typing_extensions import Literal, TypedDict
+from typing import Optional
+from typing_extensions import TypedDict
__all__ = ["OfferListParams"]
class OfferListParams(TypedDict, total=False):
- brand: str
- """Filter by brand name, case-insensitive (e.g., `Fiat`)."""
+ brand: Optional[str]
- canone_max_eur: int
- """Upper bound on displayed monthly canon (EUR)."""
+ canone_max_eur: Optional[int]
- cursor: str
- """Opaque pagination cursor."""
+ cursor: Optional[str]
- duration_months: Literal[24, 36, 48]
+ duration_months: Optional[int]
- fuel_type: str
- """Raw Italian label (case-insensitive ILIKE match).
+ fuel_type: Optional[str]
- Examples: "Benzina", "Diesel", "Ibrido benzina", "Ibrido diesel", "Elettrica",
- "GPL", "Metano".
- """
-
- km_per_year: Literal[10000, 15000, 20000, 25000, 30000, 40000]
+ km_per_year: Optional[int]
limit: int
- segment: str
- """Raw Italian label (case-insensitive ILIKE substring match).
-
- Examples: "SUV piccoli", "SUV medi", "Superiori", "Medie", "Utilitarie".
- """
+ segment: Optional[str]
diff --git a/src/partnermax/types/dealers/nlt/offer_list_response.py b/src/partnermax/types/dealers/nlt/offer_list_response.py
index 98b291d..2ffed76 100644
--- a/src/partnermax/types/dealers/nlt/offer_list_response.py
+++ b/src/partnermax/types/dealers/nlt/offer_list_response.py
@@ -9,6 +9,8 @@
class OfferListResponse(BaseModel):
+ """Cursor-paginated list of offer summaries."""
+
data: List[NltOfferSummary]
has_more: bool
diff --git a/src/partnermax/types/dealers/nlt/offer_retrieve_response.py b/src/partnermax/types/dealers/nlt/offer_retrieve_response.py
index eae704f..08879b3 100644
--- a/src/partnermax/types/dealers/nlt/offer_retrieve_response.py
+++ b/src/partnermax/types/dealers/nlt/offer_retrieve_response.py
@@ -2,7 +2,6 @@
from typing import Dict, List, Optional
from datetime import datetime
-from typing_extensions import Literal
from ...._models import BaseModel
@@ -24,43 +23,79 @@
class AvailableAddonsReplacementVehicle(BaseModel):
+ """
+ Replacement-vehicle add-on lookup (apimax: `addons_disponibili.auto_sostitutiva`).
+
+ Always category B (utilitaria) per founder decision — the spoken
+ "average customer" segment.
+ """
+
category_description: str
default_category: str
- """Replacement vehicle category (B fixed)."""
monthly_cost_eur: float
class AvailableAddonsTires(BaseModel):
+ """Tyre-replacement add-on lookup (apimax: `addons_disponibili.pneumatici`).
+
+ Populated when `mnet_dettagli.pneumatici_anteriori` matches `R\\dd+` and
+ a row exists in `nlt_pneumatici` for that diameter. Null otherwise.
+ Replacement rule (founder decision 2026-05-12): 1 set of 4 tyres
+ every 30 000 km, rounded up.
+ """
+
diameter_in: int
- """Tyre diameter in inches."""
replacement_rule: str
- """Replacement rule (e.g. one set every 30 000 km, rounded up)."""
set_cost_eur: float
- """Cost of one set of 4 tyres, EUR."""
class AvailableAddons(BaseModel):
+ """Container for optional add-ons (apimax: `addons_disponibili`)."""
+
replacement_vehicle: Optional[AvailableAddonsReplacementVehicle] = None
+ """
+ Replacement-vehicle add-on lookup (apimax:
+ `addons_disponibili.auto_sostitutiva`).
+
+ Always category B (utilitaria) per founder decision — the spoken "average
+ customer" segment.
+ """
tires: Optional[AvailableAddonsTires] = None
+ """Tyre-replacement add-on lookup (apimax: `addons_disponibili.pneumatici`).
+
+ Populated when `mnet_dettagli.pneumatici_anteriori` matches `R\\dd+` and a row
+ exists in `nlt_pneumatici` for that diameter. Null otherwise. Replacement rule
+ (founder decision 2026-05-12): 1 set of 4 tyres every 30 000 km, rounded up.
+ """
class DownPaymentScenariosEur(BaseModel):
+ """Three down-payment scenarios in EUR (whole amounts).
+
+ apimax: `anticipo_scenari_eur` (keys remapped to American English
+ snake_case for partnermax SDK: `zero/medium/standard`).
+ """
+
medium: int
- """12.5% down-payment scenario, whole EUR."""
standard: int
- """25% down-payment scenario, whole EUR (matches vetrina canon)."""
zero: int
- """Zero down-payment scenario, whole EUR."""
class DownPaymentScenariosLabels(BaseModel):
+ """Italian labels paired 1:1 with `NltDownPaymentScenariosEur`.
+
+ apimax: `anticipo_scenari_labels` — used by Custom GPT to render the
+ three options in conversation. Values stay in Italian raw
+ ("Senza anticipo" / "Anticipo 12,5%" / "Anticipo 25%").
+ """
+
medium: str
standard: str
@@ -69,23 +104,30 @@ class DownPaymentScenariosLabels(BaseModel):
class Faq(BaseModel):
- answer: str
- """
- Italian answer (no placeholders — values from the offer payload + Motornet
- specs).
+ """One Italian Q&A entry derived per-offer.
+
+ apimax: `build_offer_faqs` in `seo_engine/nlt_faq_builder.py` —
+ generates up to ~11 Q&A pairs (dimensions, fuel, transmission, CO2,
+ monthly canon at preset combo, available durations, VAT inclusion,
+ down-payment tiers, etc.). Partnermax surfaces them all, 1:1.
"""
+ answer: str
+
question: str
- """Italian question."""
class Gallery(BaseModel):
+ """One image in the offer gallery (apimax: `gallery[]`)."""
+
is_cover: bool
url: str
class IncludedAccessory(BaseModel):
+ """One accessory bundled with the offer (apimax: `accessori_inclusi[]`)."""
+
code: str
description: str
@@ -94,14 +136,29 @@ class IncludedAccessory(BaseModel):
class IncludedService(BaseModel):
+ """One NLT service normally included in the canone.
+
+ apimax: `_get_services_included` (`nlt_resolver.py:719`). Source is
+ the global `nlt_services` table (active rows only). Same set of
+ services across the network (Assicurazione RCA / Kasco /
+ Incendio-Furto, Manutenzione, Assistenza Stradale, Bollo,
+ Pneumatici, Veicolo in anticipo, Vettura sostitutiva). Not per-offer.
+ """
+
name: str
- """Service name (e.g. "Assicurazione RCA", "Manutenzione")."""
description: Optional[str] = None
- """Short human description (e.g. "Responsabilità Civile Auto")."""
class NetworkOffer(BaseModel):
+ """One network dealer's quote for this offer (apimax: `network_offers[]`).
+
+ Sorted by `min_monthly_canon_eur ASC`. In partnermax this list is
+ scoped to dealers owned by the calling partner
+ (`utenti.parent_id = partner.user_id`) — same shape as the apimax
+ cross-network list, partner-scoped to avoid data leakage.
+ """
+
dealer_id: int
dealer_name: str
@@ -124,32 +181,43 @@ class NetworkOffer(BaseModel):
class Quotation(BaseModel):
- duration_months: Literal[36, 48, 60]
+ """One priced cell of the 18-combination matrix.
- km_per_year: Literal[10000, 15000, 20000, 25000, 30000, 40000]
+ apimax: `quotazioni[]` entry — `_compute_quotazioni_dealer_aware`
+ (mcp_server.py:180). Reflects the dealer's vetrina formula applied to
+ each (durata, km) combo; cells with implausible canon (<€50) are
+ dropped upstream, so the list may contain fewer than 18 rows.
+ """
- monthly_canon_eur: float
- """Displayed monthly canon for this (duration, km) cell.
+ duration_months: int
- Computed by `calcola_canone_vetrina` for the primary dealer of the partner
- network; VAT-inclusive when `private_only=true`.
- """
+ km_per_year: int
+
+ monthly_canon_eur: float
class Tag(BaseModel):
+ """Category tag for an offer (apimax: `tags[]`).
+
+ Populated from `nlt_offerta_tag` ⋈ `nlt_offerte_tag`. Examples in
+ production: "Promo", "Stock pronto", "GreenChoice".
+ """
+
name: str
color: Optional[str] = None
- """Hex color for tag chip."""
icon: Optional[str] = None
- """FontAwesome icon class."""
class OfferRetrieveResponse(BaseModel):
"""Full offer detail.
- Field names: American English snake_case (Stripe-style SDK contract). Values: raw Italian, apimax-aligned. Shape mirrors apimax MCP `get_nlt_offer_details` (`apimax/app/api/mcp_server.py::_tool_get_nlt_offer_details`).
+ Shape mirrors `_tool_get_nlt_offer_details` (apimax MCP) with all
+ field names translated to American English snake_case for the
+ partner SDK contract. VALUES stay Italian raw (apimax-aligned).
+ The dict `technical_details` keeps Italian KEYS because they are
+ `mnet_dettagli` column names (raw DB).
"""
found: bool
@@ -157,48 +225,40 @@ class OfferRetrieveResponse(BaseModel):
network_dealer_count: int
offer_id: str
- """Numeric `nlt_offerte.id_offerta` as string.
-
- Same value returned by the listing endpoint.
- """
slug: str
- """
- Offer slug (stable identifier shared with apimax surfaces and used in canonical
- URLs).
- """
title: str
vat_included: bool
- """True when canon is VAT-inclusive (i.e. `private_only=true`)."""
available_addons: Optional[AvailableAddons] = None
+ """Container for optional add-ons (apimax: `addons_disponibili`)."""
brand: Optional[str] = None
description_full: Optional[str] = None
- """AI-generated long-form description."""
description_short: Optional[str] = None
down_payment_scenarios_eur: Optional[DownPaymentScenariosEur] = None
+ """Three down-payment scenarios in EUR (whole amounts).
+
+ apimax: `anticipo_scenari_eur` (keys remapped to American English snake_case for
+ partnermax SDK: `zero/medium/standard`).
+ """
down_payment_scenarios_labels: Optional[DownPaymentScenariosLabels] = None
+ """Italian labels paired 1:1 with `NltDownPaymentScenariosEur`.
- faqs: Optional[List[Faq]] = None
- """
- Italian FAQ for this offer — apimax `build_offer_faqs`
- (`seo_engine/nlt_faq_builder.py:63`), no cap. Derived from offer payload +
- Motornet specs (dimensioni, bagagliaio, CO2, motore, posti/porte, prestazioni,
- canone preset 48/15k, canone minimo, durate, IVA, anticipi).
+ apimax: `anticipo_scenari_labels` — used by Custom GPT to render the three
+ options in conversation. Values stay in Italian raw ("Senza anticipo" /
+ "Anticipo 12,5%" / "Anticipo 25%").
"""
- fuel_type: Optional[str] = None
- """Raw Italian label from `nlt_offerte.alimentazione` (e.g.
+ faqs: Optional[List[Faq]] = None
- "Benzina", "Ibrido diesel").
- """
+ fuel_type: Optional[str] = None
gallery: Optional[List[Gallery]] = None
@@ -207,23 +267,14 @@ class OfferRetrieveResponse(BaseModel):
included_accessories: Optional[List[IncludedAccessory]] = None
included_services: Optional[List[IncludedService]] = None
- """Services normally included in the canone.
-
- apimax: `_get_services_included` (`nlt_resolver.py:719`) reads global
- `nlt_services` is_active table.
- """
last_modified: Optional[datetime] = None
min_monthly_canon_eur: Optional[float] = None
- """
- Lowest canon across the partner network for this offer (primary dealer's price).
- """
model: Optional[str] = None
network_offers: Optional[List[NetworkOffer]] = None
- """All the partner's dealers that can fulfil this offer, sorted by canon ASC."""
primary_dealer_city: Optional[str] = None
@@ -232,47 +283,19 @@ class OfferRetrieveResponse(BaseModel):
primary_dealer_province: Optional[str] = None
private_only: Optional[bool] = None
- """Per-offer VAT scope: true → consumer-facing (B2C, VAT-inclusive).
-
- false → business (B2B, VAT-exclusive). Sourced from `nlt_offerte.solo_privati`.
- """
quotations: Optional[List[Quotation]] = None
segment: Optional[str] = None
- """Raw Italian label from `nlt_offerte.segmento` (e.g.
-
- "SUV piccoli", "Superiori").
- """
standard_equipment: Optional[List[str]] = None
- """Standard equipment list (one entry per item).
-
- Sourced from `mnet_dettagli.equipaggiamento` split on newlines/semicolons.
- Currently empty on every live offer (upstream column unpopulated); will
- auto-fill when the data flows in.
- """
tags: Optional[List[Tag]] = None
technical_details: Optional[Dict[str, object]] = None
- """
- Full Motornet technical sheet — apimax: `_get_dettagli_motornet`
- (`nlt_resolver.py:752`). Every non-null `mnet_dettagli` column for this
- `codice_motornet_uni` flattened into a plain dict (~30-40 keys typically
- populated out of 90 columns). KEYS stay Italian because they are raw SQL column
- names: cilindrata (cc), kw, hp, coppia, accelerazione (s), velocita (km/h),
- lunghezza/larghezza/altezza/passo (cm), peso (kg), bagagliaio (L, free-text),
- emissioni_co2 (g/km, free-text), pneumatici_anteriori ("205/55 R17"), trazione,
- alimentazione, cambio, euro, autonomia_media, capacita_nominale_batteria, etc.
- Native units preserved; values are int/float/bool/string (timestamps
- ISO-formatted).
- """
total_price_eur: Optional[float] = None
- """List price IVA-inclusive (vehicle + accessories + MSS)."""
transmission: Optional[str] = None
- """Raw Italian label from `nlt_offerte.cambio` (e.g. "Automatico sequenziale")."""
trim: Optional[str] = None
diff --git a/src/partnermax/types/dealers/nlt_setting_update_params.py b/src/partnermax/types/dealers/nlt_setting_update_params.py
index f58d35c..ec33f04 100644
--- a/src/partnermax/types/dealers/nlt_setting_update_params.py
+++ b/src/partnermax/types/dealers/nlt_setting_update_params.py
@@ -3,9 +3,8 @@
from __future__ import annotations
from typing import Optional
-from typing_extensions import Literal, Required, Annotated, TypedDict
+from typing_extensions import Literal, Required, TypedDict
-from ..._utils import PropertyInfo
from .down_payment_tiers_param import DownPaymentTiersParam
__all__ = ["NltSettingUpdateParams"]
@@ -17,16 +16,15 @@ class NltSettingUpdateParams(TypedDict, total=False):
down_payment_tiers: Required[DownPaymentTiersParam]
"""Three down-payment scenarios (basso / medio / alto).
- Each tier carries `{percent_of_list (0–100), fixed_eur (≥0)}`. No
- strict-ascending check — the final EUR per tier is offer-dependent
- (`listino_imponibile * pct + eur`).
+ No strict-ascending validation: the final EUR amount depends on the offer's list
+ price (`tier.percent_of_list / 100 * listino_imponibile + tier.fixed_eur`), so a
+ tier that looks larger by % can produce a smaller EUR on cheap vehicles. Label
+ semantics (low/medium/high) are advisory — apimax/DealerMAX UI treats the 3
+ positions as opaque slots ordered by intent.
"""
- image_mode: Required[Literal["branded", "scenario_locked", "scenario_seasonal"]]
-
currency: Literal["EUR"]
- image_scenario_locked: Optional[Literal["mediterraneo", "cortina", "milano", "showroom"]]
- """Required when `image_mode='scenario_locked'`; must be null otherwise."""
+ image_mode: Literal["branded", "scenario_locked", "scenario_seasonal"]
- idempotency_key: Annotated[str, PropertyInfo(alias="Idempotency-Key")]
+ image_scenario_locked: Optional[Literal["mediterraneo", "cortina", "milano", "showroom"]]
diff --git a/src/partnermax/types/dealers/nlt_settings.py b/src/partnermax/types/dealers/nlt_settings.py
index 635500f..2f2ebc8 100644
--- a/src/partnermax/types/dealers/nlt_settings.py
+++ b/src/partnermax/types/dealers/nlt_settings.py
@@ -11,39 +11,31 @@
class NltSettings(BaseModel):
- """Dealer-level NLT economics + image rendering preferences.
+ """Response model for GET / PATCH /v1/dealers/{id}/nlt-settings.
- VAT treatment is NOT a dealer-level field — it is a property of the offer (see `NltOfferSummary.vat_treatment`).
+ Note: there is no `vat_treatment` field — VAT is a property of the
+ offer (`nlt_offerte.solo_privati`), not of the dealer. The offer
+ detail returns the VAT treatment per row instead.
"""
agency_markup_percent: float
- """Markup applied on top of the network base canon, in percent. Hard cap at 10%."""
-
- currency: Literal["EUR"]
- """Only EUR supported in v1."""
dealer_id: str
down_payment_tiers: DownPaymentTiers
"""Three down-payment scenarios (basso / medio / alto).
- Each tier carries `{percent_of_list (0–100), fixed_eur (≥0)}`. No
- strict-ascending check — the final EUR per tier is offer-dependent
- (`listino_imponibile * pct + eur`).
+ No strict-ascending validation: the final EUR amount depends on the offer's list
+ price (`tier.percent_of_list / 100 * listino_imponibile + tier.fixed_eur`), so a
+ tier that looks larger by % can produce a smaller EUR on cheap vehicles. Label
+ semantics (low/medium/high) are advisory — apimax/DealerMAX UI treats the 3
+ positions as opaque slots ordered by intent.
"""
effective_from: datetime
- image_mode: Literal["branded", "scenario_locked", "scenario_seasonal"]
- """
- How NLT offer cover images are rendered for this dealer (apimax:
- `nlt_image_mode`). `branded` (default): per-dealer composite. `scenario_locked`:
- single AI scenario fixed by the dealer. `scenario_seasonal`: AI scenario
- auto-rotated by Italian season.
- """
+ currency: Optional[Literal["EUR"]] = None
- image_scenario_locked: Optional[Literal["mediterraneo", "cortina", "milano", "showroom"]] = None
- """Only set when `image_mode='scenario_locked'`.
+ image_mode: Optional[Literal["branded", "scenario_locked", "scenario_seasonal"]] = None
- One of the four AI scenarios available on `mnet_modelli_ai_foto.scenario`.
- """
+ image_scenario_locked: Optional[Literal["mediterraneo", "cortina", "milano", "showroom"]] = None
diff --git a/src/partnermax/types/dealers/vehicle_bulk_params.py b/src/partnermax/types/dealers/vehicle_bulk_params.py
new file mode 100644
index 0000000..38d9fcb
--- /dev/null
+++ b/src/partnermax/types/dealers/vehicle_bulk_params.py
@@ -0,0 +1,130 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from typing import Union, Iterable, Optional
+from datetime import date
+from typing_extensions import Required, Annotated, TypedDict
+
+from ..._utils import PropertyInfo
+
+__all__ = ["VehicleBulkParams", "Vehicle"]
+
+
+class VehicleBulkParams(TypedDict, total=False):
+ vehicles: Required[Iterable[Vehicle]]
+ """Array of vehicles to create.
+
+ Between 1 and 100 rows per call. For larger imports, the partner is expected to
+ chunk client-side (e.g. 50 calls of 100 rows each for a 5 000-vehicle
+ migration).
+ """
+
+ idempotency_key: Annotated[str, PropertyInfo(alias="Idempotency-Key")]
+
+
+class Vehicle(TypedDict, total=False):
+ """Request body for vehicle provisioning.
+
+ The partner sends a small, vehicle-specific payload. All technical specs
+ (brand, model, trim, fuel type, displacement, dimensions, CO2, etc.) are
+ derived server-side from `mnet_dettagli` via the `motornet_code` join —
+ the partner never types them. This is the same canonical pattern used by
+ NLT offers and matches the platform rule
+ `feedback_motornet_authoritative`.
+
+ Fields immutable after creation: `motornet_code`, `plate`, `vin`. Other
+ fields may be updated via PATCH.
+ """
+
+ certified_km: Required[int]
+ """Certified odometer reading at intake, in kilometres."""
+
+ cost_price_eur: Required[float]
+ """Cost basis to the dealer in EUR (partner/dealer internal).
+
+ Not surfaced on consumer-facing AI surfaces; used by dealer reporting and margin
+ analytics only.
+ """
+
+ motornet_code: Required[str]
+ """Motornet UNI code identifying the exact vehicle configuration.
+
+ Must exist in `mnet_dettagli_usato` at submission time; otherwise the call
+ returns 422 `motornet_code_not_in_catalogue`. The partner is expected to source
+ this from its own DMS; partnermax does not expose a plate→code lookup.
+ """
+
+ plate: Required[str]
+ """Italian licence plate.
+
+ Uppercased server-side. UNIQUE across the network for active vehicles
+ (`visibile=true AND venduto_il IS NULL`); reusable once the previous holder
+ sells/hides the row.
+ """
+
+ registration_year: Required[int]
+ """Year of first registration. Upper bound is current year + 1."""
+
+ sale_price_eur: Required[float]
+ """Public sale price in EUR.
+
+ Surfaced on MCP / Custom GPT / NLWeb and on the dealer's site JSON-LD
+ `Offer.price`.
+ """
+
+ alloy_wheel_size: Optional[int]
+
+ color: Optional[str]
+
+ description: str
+ """Partner-supplied long description. Surfaced on the dealer site detail page."""
+
+ extended_warranty_enabled: bool
+
+ extended_warranty_months: Optional[int]
+
+ inspection_expiry_date: Annotated[Union[str, date, None], PropertyInfo(format="iso8601")]
+
+ is_for_sale: bool
+ """Maps to `azlease_usatoauto.is_vendita_enabled`.
+
+ When false the row is in stock but not offered for sale.
+ """
+
+ is_visible: bool
+ """Soft-publish flag.
+
+ When false the row exists in stock but is excluded from consumer-facing AI
+ surfaces. Maps to `azlease_usatoin.visibile`.
+ """
+
+ last_service_date: Annotated[Union[str, date, None], PropertyInfo(format="iso8601")]
+
+ last_service_km: Optional[int]
+
+ last_service_notes: Optional[str]
+
+ notes: Optional[str]
+ """Free-form short notes; surfaced as `mnet_dettagli.precisazioni`-style."""
+
+ previous_owner_count: Optional[int]
+
+ previous_ownership_transfer_date: Annotated[Union[str, date, None], PropertyInfo(format="iso8601")]
+ """Date of the most recent ownership transfer, if known."""
+
+ registration_month: Optional[int]
+ """Month of registration (1–12)."""
+
+ road_tax_expiry_date: Annotated[Union[str, date, None], PropertyInfo(format="iso8601")]
+
+ vat_displayed: bool
+ """
+ If true the public price is displayed VAT-exposed (B2B); otherwise VAT-inclusive
+ (B2C).
+ """
+
+ vehicle_damaged: bool
+
+ vin: Optional[str]
+ """ISO 3779 vehicle identification number. Optional but strongly recommended."""
diff --git a/src/partnermax/types/dealers/vehicle_create_params.py b/src/partnermax/types/dealers/vehicle_create_params.py
new file mode 100644
index 0000000..ed80776
--- /dev/null
+++ b/src/partnermax/types/dealers/vehicle_create_params.py
@@ -0,0 +1,107 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from typing import Union, Optional
+from datetime import date
+from typing_extensions import Required, Annotated, TypedDict
+
+from ..._utils import PropertyInfo
+
+__all__ = ["VehicleCreateParams"]
+
+
+class VehicleCreateParams(TypedDict, total=False):
+ certified_km: Required[int]
+ """Certified odometer reading at intake, in kilometres."""
+
+ cost_price_eur: Required[float]
+ """Cost basis to the dealer in EUR (partner/dealer internal).
+
+ Not surfaced on consumer-facing AI surfaces; used by dealer reporting and margin
+ analytics only.
+ """
+
+ motornet_code: Required[str]
+ """Motornet UNI code identifying the exact vehicle configuration.
+
+ Must exist in `mnet_dettagli_usato` at submission time; otherwise the call
+ returns 422 `motornet_code_not_in_catalogue`. The partner is expected to source
+ this from its own DMS; partnermax does not expose a plate→code lookup.
+ """
+
+ plate: Required[str]
+ """Italian licence plate.
+
+ Uppercased server-side. UNIQUE across the network for active vehicles
+ (`visibile=true AND venduto_il IS NULL`); reusable once the previous holder
+ sells/hides the row.
+ """
+
+ registration_year: Required[int]
+ """Year of first registration. Upper bound is current year + 1."""
+
+ sale_price_eur: Required[float]
+ """Public sale price in EUR.
+
+ Surfaced on MCP / Custom GPT / NLWeb and on the dealer's site JSON-LD
+ `Offer.price`.
+ """
+
+ alloy_wheel_size: Optional[int]
+
+ color: Optional[str]
+
+ description: str
+ """Partner-supplied long description. Surfaced on the dealer site detail page."""
+
+ extended_warranty_enabled: bool
+
+ extended_warranty_months: Optional[int]
+
+ inspection_expiry_date: Annotated[Union[str, date, None], PropertyInfo(format="iso8601")]
+
+ is_for_sale: bool
+ """Maps to `azlease_usatoauto.is_vendita_enabled`.
+
+ When false the row is in stock but not offered for sale.
+ """
+
+ is_visible: bool
+ """Soft-publish flag.
+
+ When false the row exists in stock but is excluded from consumer-facing AI
+ surfaces. Maps to `azlease_usatoin.visibile`.
+ """
+
+ last_service_date: Annotated[Union[str, date, None], PropertyInfo(format="iso8601")]
+
+ last_service_km: Optional[int]
+
+ last_service_notes: Optional[str]
+
+ notes: Optional[str]
+ """Free-form short notes; surfaced as `mnet_dettagli.precisazioni`-style."""
+
+ previous_owner_count: Optional[int]
+
+ previous_ownership_transfer_date: Annotated[Union[str, date, None], PropertyInfo(format="iso8601")]
+ """Date of the most recent ownership transfer, if known."""
+
+ registration_month: Optional[int]
+ """Month of registration (1–12)."""
+
+ road_tax_expiry_date: Annotated[Union[str, date, None], PropertyInfo(format="iso8601")]
+
+ vat_displayed: bool
+ """
+ If true the public price is displayed VAT-exposed (B2B); otherwise VAT-inclusive
+ (B2C).
+ """
+
+ vehicle_damaged: bool
+
+ vin: Optional[str]
+ """ISO 3779 vehicle identification number. Optional but strongly recommended."""
+
+ idempotency_key: Annotated[str, PropertyInfo(alias="Idempotency-Key")]
diff --git a/src/partnermax/types/dealers/vehicle_detail.py b/src/partnermax/types/dealers/vehicle_detail.py
new file mode 100644
index 0000000..e25fce1
--- /dev/null
+++ b/src/partnermax/types/dealers/vehicle_detail.py
@@ -0,0 +1,100 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from typing import Dict, List, Optional
+from datetime import date, datetime
+
+from ..._models import BaseModel
+
+__all__ = ["VehicleDetail"]
+
+
+class VehicleDetail(BaseModel):
+ """Full vehicle resource.
+
+ Returned by `GET /v1/dealers/{id}/vehicles/{id}`,
+ `POST /v1/dealers/{id}/vehicles`, and `PATCH /v1/dealers/{id}/vehicles/{id}`.
+
+ `technical_details` carries the flat Motornet specs dict (Italian column
+ names as keys: `cilindrata`, `kw`, `hp`, `lunghezza`, `consumo_medio`,
+ `emissioni_co2`, etc.). Same shape conventions as `NltOfferDetail`
+ (`feedback_partnermax_field_naming_us_english`: field names are
+ English snake_case, raw catalogue values stay verbatim).
+ """
+
+ certified_km: int
+
+ cost_price_eur: float
+
+ created_at: datetime
+
+ dealer_id: str
+
+ description: str
+
+ extended_warranty_enabled: bool
+
+ is_for_sale: bool
+
+ is_visible: bool
+
+ last_modified_at: datetime
+
+ motornet_code: str
+
+ partner_id: str
+
+ plate: str
+
+ registration_year: int
+
+ sale_price_eur: float
+
+ vat_displayed: bool
+
+ vehicle_damaged: bool
+
+ vehicle_id: str
+
+ alloy_wheel_size: Optional[int] = None
+
+ brand: Optional[str] = None
+
+ color: Optional[str] = None
+
+ extended_warranty_months: Optional[int] = None
+
+ fuel_type: Optional[str] = None
+
+ image_urls: Optional[List[str]] = None
+ """Vehicle photos. Empty in v1 (media upload ships in v1.2)."""
+
+ inspection_expiry_date: Optional[date] = None
+
+ last_service_date: Optional[date] = None
+
+ last_service_km: Optional[int] = None
+
+ last_service_notes: Optional[str] = None
+
+ model: Optional[str] = None
+
+ notes: Optional[str] = None
+
+ previous_owner_count: Optional[int] = None
+
+ previous_ownership_transfer_date: Optional[date] = None
+
+ registration_month: Optional[int] = None
+
+ road_tax_expiry_date: Optional[date] = None
+
+ technical_details: Optional[Dict[str, object]] = None
+ """
+ Flat dict of every non-null `mnet_dettagli_usato` column for this
+ `motornet_code`. Keys stay in Italian because they are raw SQL column names;
+ native units preserved (mm, kg, kW, CV, g/km, etc.).
+ """
+
+ trim: Optional[str] = None
+
+ vin: Optional[str] = None
diff --git a/src/partnermax/types/dealers/vehicle_list.py b/src/partnermax/types/dealers/vehicle_list.py
new file mode 100644
index 0000000..94c68a9
--- /dev/null
+++ b/src/partnermax/types/dealers/vehicle_list.py
@@ -0,0 +1,18 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from typing import List, Optional
+
+from ..._models import BaseModel
+from .vehicle_summary import VehicleSummary
+
+__all__ = ["VehicleList"]
+
+
+class VehicleList(BaseModel):
+ """Cursor-paginated list of vehicle summaries."""
+
+ data: List[VehicleSummary]
+
+ has_more: bool
+
+ next_cursor: Optional[str] = None
diff --git a/src/partnermax/types/dealers/vehicle_list_params.py b/src/partnermax/types/dealers/vehicle_list_params.py
new file mode 100644
index 0000000..3380df1
--- /dev/null
+++ b/src/partnermax/types/dealers/vehicle_list_params.py
@@ -0,0 +1,26 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from typing import Optional
+from typing_extensions import TypedDict
+
+__all__ = ["VehicleListParams"]
+
+
+class VehicleListParams(TypedDict, total=False):
+ cursor: Optional[str]
+
+ include_deleted: bool
+ """If true, soft-deleted rows (`venduto_il` populated) are also returned.
+
+ Default false — listings hide soft-deleted vehicles.
+ """
+
+ is_for_sale: Optional[bool]
+ """Filter on the sale flag."""
+
+ is_visible: Optional[bool]
+ """Filter on the visibility flag."""
+
+ limit: int
diff --git a/src/partnermax/types/dealers/vehicle_retrieve_params.py b/src/partnermax/types/dealers/vehicle_retrieve_params.py
new file mode 100644
index 0000000..7cb721d
--- /dev/null
+++ b/src/partnermax/types/dealers/vehicle_retrieve_params.py
@@ -0,0 +1,18 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from typing_extensions import Required, TypedDict
+
+__all__ = ["VehicleRetrieveParams"]
+
+
+class VehicleRetrieveParams(TypedDict, total=False):
+ dealer_id: Required[str]
+
+ include_deleted: bool
+ """If true, the detail of a soft-deleted vehicle is returned.
+
+ Default false — soft-deleted rows return 404 to keep behaviour consistent with
+ the list endpoint.
+ """
diff --git a/src/partnermax/types/dealers/vehicle_summary.py b/src/partnermax/types/dealers/vehicle_summary.py
new file mode 100644
index 0000000..50d90f2
--- /dev/null
+++ b/src/partnermax/types/dealers/vehicle_summary.py
@@ -0,0 +1,47 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from typing import Optional
+from datetime import datetime
+
+from ..._models import BaseModel
+
+__all__ = ["VehicleSummary"]
+
+
+class VehicleSummary(BaseModel):
+ """Compact vehicle payload for list endpoints.
+
+ Catalogue fields (`brand`, `model`, `trim`, `fuel_type`) are derived from
+ `mnet_dettagli` at read time. Italian raw labels are surfaced verbatim —
+ same convention as NLT (`apimax`-aligned).
+ """
+
+ certified_km: int
+
+ created_at: datetime
+
+ dealer_id: str
+
+ is_for_sale: bool
+
+ is_visible: bool
+
+ motornet_code: str
+
+ plate: str
+
+ registration_year: int
+
+ sale_price_eur: float
+
+ vehicle_id: str
+
+ brand: Optional[str] = None
+
+ color: Optional[str] = None
+
+ fuel_type: Optional[str] = None
+
+ model: Optional[str] = None
+
+ trim: Optional[str] = None
diff --git a/src/partnermax/types/dealers/vehicle_update_params.py b/src/partnermax/types/dealers/vehicle_update_params.py
new file mode 100644
index 0000000..5707c59
--- /dev/null
+++ b/src/partnermax/types/dealers/vehicle_update_params.py
@@ -0,0 +1,59 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from typing import Union, Optional
+from datetime import date
+from typing_extensions import Required, Annotated, TypedDict
+
+from ..._utils import PropertyInfo
+
+__all__ = ["VehicleUpdateParams"]
+
+
+class VehicleUpdateParams(TypedDict, total=False):
+ dealer_id: Required[str]
+
+ alloy_wheel_size: Optional[int]
+
+ certified_km: Optional[int]
+
+ color: Optional[str]
+
+ cost_price_eur: Optional[float]
+
+ description: Optional[str]
+
+ extended_warranty_enabled: Optional[bool]
+
+ extended_warranty_months: Optional[int]
+
+ inspection_expiry_date: Annotated[Union[str, date, None], PropertyInfo(format="iso8601")]
+
+ is_for_sale: Optional[bool]
+
+ is_visible: Optional[bool]
+
+ last_service_date: Annotated[Union[str, date, None], PropertyInfo(format="iso8601")]
+
+ last_service_km: Optional[int]
+
+ last_service_notes: Optional[str]
+
+ notes: Optional[str]
+
+ previous_owner_count: Optional[int]
+
+ previous_ownership_transfer_date: Annotated[Union[str, date, None], PropertyInfo(format="iso8601")]
+
+ registration_month: Optional[int]
+
+ road_tax_expiry_date: Annotated[Union[str, date, None], PropertyInfo(format="iso8601")]
+
+ sale_price_eur: Optional[float]
+
+ vat_displayed: Optional[bool]
+
+ vehicle_damaged: Optional[bool]
+
+ idempotency_key: Annotated[str, PropertyInfo(alias="Idempotency-Key")]
diff --git a/src/partnermax/types/key_issue_params.py b/src/partnermax/types/key_issue_params.py
index f45ddac..04214a2 100644
--- a/src/partnermax/types/key_issue_params.py
+++ b/src/partnermax/types/key_issue_params.py
@@ -15,7 +15,7 @@ class KeyIssueParams(TypedDict, total=False):
label: Required[str]
"""Human-readable identifier for this key, used for safe logging."""
- expires_at: Annotated[Union[str, datetime], PropertyInfo(format="iso8601")]
+ expires_at: Annotated[Union[str, datetime, None], PropertyInfo(format="iso8601")]
"""Optional expiry timestamp. Null = never expires until revoked."""
idempotency_key: Annotated[str, PropertyInfo(alias="Idempotency-Key")]
diff --git a/src/partnermax/types/key_issue_response.py b/src/partnermax/types/key_issue_response.py
index e6285c4..487c09a 100644
--- a/src/partnermax/types/key_issue_response.py
+++ b/src/partnermax/types/key_issue_response.py
@@ -1,5 +1,6 @@
# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+from typing import Optional
from datetime import datetime
from .._models import BaseModel
@@ -8,19 +9,21 @@
class KeyIssueResponse(BaseModel):
+ """One-time response for ``POST /v1/keys/issue``.
+
+ The ``key`` field is plaintext and is never retrievable again. Callers
+ must persist it immediately on receipt.
+ """
+
created_at: datetime
- expires_at: datetime
+ expires_at: Optional[datetime] = None
key: str
- """Plaintext key material.
-
- Returned ONCE — never retrievable again. Store securely.
- """
+ """Plaintext key material. Returned ONCE — never retrievable again."""
key_id: str
key_prefix: str
- """First 8 characters of the key, safe for logging."""
label: str
diff --git a/src/partnermax/types/key_list_response.py b/src/partnermax/types/key_list_response.py
index 75291d3..3ed2ec1 100644
--- a/src/partnermax/types/key_list_response.py
+++ b/src/partnermax/types/key_list_response.py
@@ -9,8 +9,12 @@
class Data(BaseModel):
+ """Metadata-only representation of an API key. Safe to return on list calls."""
+
created_at: datetime
+ expires_at: Optional[datetime] = None
+
is_active: bool
key_id: str
@@ -19,10 +23,10 @@ class Data(BaseModel):
label: str
- expires_at: Optional[datetime] = None
-
last_used_at: Optional[datetime] = None
class KeyListResponse(BaseModel):
+ """Response envelope for ``GET /v1/keys``."""
+
data: List[Data]
diff --git a/tests/api_resources/dealers/nlt/test_offers.py b/tests/api_resources/dealers/nlt/test_offers.py
index 47df2f3..68b54a1 100644
--- a/tests/api_resources/dealers/nlt/test_offers.py
+++ b/tests/api_resources/dealers/nlt/test_offers.py
@@ -85,9 +85,9 @@ def test_method_list_with_all_params(self, client: Partnermax) -> None:
brand="brand",
canone_max_eur=50,
cursor="cursor",
- duration_months=24,
+ duration_months=0,
fuel_type="fuel_type",
- km_per_year=10000,
+ km_per_year=0,
limit=1,
segment="segment",
)
@@ -201,9 +201,9 @@ async def test_method_list_with_all_params(self, async_client: AsyncPartnermax)
brand="brand",
canone_max_eur=50,
cursor="cursor",
- duration_months=24,
+ duration_months=0,
fuel_type="fuel_type",
- km_per_year=10000,
+ km_per_year=0,
limit=1,
segment="segment",
)
diff --git a/tests/api_resources/dealers/test_nlt_settings.py b/tests/api_resources/dealers/test_nlt_settings.py
index 43b0546..98bca81 100644
--- a/tests/api_resources/dealers/test_nlt_settings.py
+++ b/tests/api_resources/dealers/test_nlt_settings.py
@@ -64,11 +64,11 @@ def test_path_params_retrieve(self, client: Partnermax) -> None:
def test_method_update(self, client: Partnermax) -> None:
nlt_setting = client.dealers.nlt_settings.update(
dealer_id="dealer_id",
- agency_markup_percent=3.5,
+ agency_markup_percent=0,
down_payment_tiers={
"high": {
"fixed_eur": 0,
- "percent_of_list": 25,
+ "percent_of_list": 0,
},
"low": {
"fixed_eur": 0,
@@ -76,10 +76,9 @@ def test_method_update(self, client: Partnermax) -> None:
},
"medium": {
"fixed_eur": 0,
- "percent_of_list": 12.5,
+ "percent_of_list": 0,
},
},
- image_mode="branded",
)
assert_matches_type(NltSettings, nlt_setting, path=["response"])
@@ -88,11 +87,11 @@ def test_method_update(self, client: Partnermax) -> None:
def test_method_update_with_all_params(self, client: Partnermax) -> None:
nlt_setting = client.dealers.nlt_settings.update(
dealer_id="dealer_id",
- agency_markup_percent=3.5,
+ agency_markup_percent=0,
down_payment_tiers={
"high": {
"fixed_eur": 0,
- "percent_of_list": 25,
+ "percent_of_list": 0,
},
"low": {
"fixed_eur": 0,
@@ -100,13 +99,12 @@ def test_method_update_with_all_params(self, client: Partnermax) -> None:
},
"medium": {
"fixed_eur": 0,
- "percent_of_list": 12.5,
+ "percent_of_list": 0,
},
},
- image_mode="branded",
currency="EUR",
+ image_mode="branded",
image_scenario_locked="mediterraneo",
- idempotency_key="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
)
assert_matches_type(NltSettings, nlt_setting, path=["response"])
@@ -115,11 +113,11 @@ def test_method_update_with_all_params(self, client: Partnermax) -> None:
def test_raw_response_update(self, client: Partnermax) -> None:
response = client.dealers.nlt_settings.with_raw_response.update(
dealer_id="dealer_id",
- agency_markup_percent=3.5,
+ agency_markup_percent=0,
down_payment_tiers={
"high": {
"fixed_eur": 0,
- "percent_of_list": 25,
+ "percent_of_list": 0,
},
"low": {
"fixed_eur": 0,
@@ -127,10 +125,9 @@ def test_raw_response_update(self, client: Partnermax) -> None:
},
"medium": {
"fixed_eur": 0,
- "percent_of_list": 12.5,
+ "percent_of_list": 0,
},
},
- image_mode="branded",
)
assert response.is_closed is True
@@ -143,11 +140,11 @@ def test_raw_response_update(self, client: Partnermax) -> None:
def test_streaming_response_update(self, client: Partnermax) -> None:
with client.dealers.nlt_settings.with_streaming_response.update(
dealer_id="dealer_id",
- agency_markup_percent=3.5,
+ agency_markup_percent=0,
down_payment_tiers={
"high": {
"fixed_eur": 0,
- "percent_of_list": 25,
+ "percent_of_list": 0,
},
"low": {
"fixed_eur": 0,
@@ -155,10 +152,9 @@ def test_streaming_response_update(self, client: Partnermax) -> None:
},
"medium": {
"fixed_eur": 0,
- "percent_of_list": 12.5,
+ "percent_of_list": 0,
},
},
- image_mode="branded",
) as response:
assert not response.is_closed
assert response.http_request.headers.get("X-Stainless-Lang") == "python"
@@ -174,11 +170,11 @@ def test_path_params_update(self, client: Partnermax) -> None:
with pytest.raises(ValueError, match=r"Expected a non-empty value for `dealer_id` but received ''"):
client.dealers.nlt_settings.with_raw_response.update(
dealer_id="",
- agency_markup_percent=3.5,
+ agency_markup_percent=0,
down_payment_tiers={
"high": {
"fixed_eur": 0,
- "percent_of_list": 25,
+ "percent_of_list": 0,
},
"low": {
"fixed_eur": 0,
@@ -186,10 +182,9 @@ def test_path_params_update(self, client: Partnermax) -> None:
},
"medium": {
"fixed_eur": 0,
- "percent_of_list": 12.5,
+ "percent_of_list": 0,
},
},
- image_mode="branded",
)
@@ -245,11 +240,11 @@ async def test_path_params_retrieve(self, async_client: AsyncPartnermax) -> None
async def test_method_update(self, async_client: AsyncPartnermax) -> None:
nlt_setting = await async_client.dealers.nlt_settings.update(
dealer_id="dealer_id",
- agency_markup_percent=3.5,
+ agency_markup_percent=0,
down_payment_tiers={
"high": {
"fixed_eur": 0,
- "percent_of_list": 25,
+ "percent_of_list": 0,
},
"low": {
"fixed_eur": 0,
@@ -257,10 +252,9 @@ async def test_method_update(self, async_client: AsyncPartnermax) -> None:
},
"medium": {
"fixed_eur": 0,
- "percent_of_list": 12.5,
+ "percent_of_list": 0,
},
},
- image_mode="branded",
)
assert_matches_type(NltSettings, nlt_setting, path=["response"])
@@ -269,11 +263,11 @@ async def test_method_update(self, async_client: AsyncPartnermax) -> None:
async def test_method_update_with_all_params(self, async_client: AsyncPartnermax) -> None:
nlt_setting = await async_client.dealers.nlt_settings.update(
dealer_id="dealer_id",
- agency_markup_percent=3.5,
+ agency_markup_percent=0,
down_payment_tiers={
"high": {
"fixed_eur": 0,
- "percent_of_list": 25,
+ "percent_of_list": 0,
},
"low": {
"fixed_eur": 0,
@@ -281,13 +275,12 @@ async def test_method_update_with_all_params(self, async_client: AsyncPartnermax
},
"medium": {
"fixed_eur": 0,
- "percent_of_list": 12.5,
+ "percent_of_list": 0,
},
},
- image_mode="branded",
currency="EUR",
+ image_mode="branded",
image_scenario_locked="mediterraneo",
- idempotency_key="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
)
assert_matches_type(NltSettings, nlt_setting, path=["response"])
@@ -296,11 +289,11 @@ async def test_method_update_with_all_params(self, async_client: AsyncPartnermax
async def test_raw_response_update(self, async_client: AsyncPartnermax) -> None:
response = await async_client.dealers.nlt_settings.with_raw_response.update(
dealer_id="dealer_id",
- agency_markup_percent=3.5,
+ agency_markup_percent=0,
down_payment_tiers={
"high": {
"fixed_eur": 0,
- "percent_of_list": 25,
+ "percent_of_list": 0,
},
"low": {
"fixed_eur": 0,
@@ -308,10 +301,9 @@ async def test_raw_response_update(self, async_client: AsyncPartnermax) -> None:
},
"medium": {
"fixed_eur": 0,
- "percent_of_list": 12.5,
+ "percent_of_list": 0,
},
},
- image_mode="branded",
)
assert response.is_closed is True
@@ -324,11 +316,11 @@ async def test_raw_response_update(self, async_client: AsyncPartnermax) -> None:
async def test_streaming_response_update(self, async_client: AsyncPartnermax) -> None:
async with async_client.dealers.nlt_settings.with_streaming_response.update(
dealer_id="dealer_id",
- agency_markup_percent=3.5,
+ agency_markup_percent=0,
down_payment_tiers={
"high": {
"fixed_eur": 0,
- "percent_of_list": 25,
+ "percent_of_list": 0,
},
"low": {
"fixed_eur": 0,
@@ -336,10 +328,9 @@ async def test_streaming_response_update(self, async_client: AsyncPartnermax) ->
},
"medium": {
"fixed_eur": 0,
- "percent_of_list": 12.5,
+ "percent_of_list": 0,
},
},
- image_mode="branded",
) as response:
assert not response.is_closed
assert response.http_request.headers.get("X-Stainless-Lang") == "python"
@@ -355,11 +346,11 @@ async def test_path_params_update(self, async_client: AsyncPartnermax) -> None:
with pytest.raises(ValueError, match=r"Expected a non-empty value for `dealer_id` but received ''"):
await async_client.dealers.nlt_settings.with_raw_response.update(
dealer_id="",
- agency_markup_percent=3.5,
+ agency_markup_percent=0,
down_payment_tiers={
"high": {
"fixed_eur": 0,
- "percent_of_list": 25,
+ "percent_of_list": 0,
},
"low": {
"fixed_eur": 0,
@@ -367,8 +358,7 @@ async def test_path_params_update(self, async_client: AsyncPartnermax) -> None:
},
"medium": {
"fixed_eur": 0,
- "percent_of_list": 12.5,
+ "percent_of_list": 0,
},
},
- image_mode="branded",
)
diff --git a/tests/api_resources/dealers/test_vehicles.py b/tests/api_resources/dealers/test_vehicles.py
new file mode 100644
index 0000000..53f0af6
--- /dev/null
+++ b/tests/api_resources/dealers/test_vehicles.py
@@ -0,0 +1,973 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+import os
+from typing import Any, cast
+
+import pytest
+
+from partnermax import Partnermax, AsyncPartnermax
+from tests.utils import assert_matches_type
+from partnermax._utils import parse_date
+from partnermax.types.dealers import (
+ VehicleList,
+ VehicleDetail,
+ BulkCreateVehiclesResponse,
+)
+
+base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010")
+
+
+class TestVehicles:
+ parametrize = pytest.mark.parametrize("client", [False, True], indirect=True, ids=["loose", "strict"])
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ def test_method_create(self, client: Partnermax) -> None:
+ vehicle = client.dealers.vehicles.create(
+ dealer_id="dealer_id",
+ certified_km=0,
+ cost_price_eur=100,
+ motornet_code="xxxx",
+ plate="26F1KLZN",
+ registration_year=1960,
+ sale_price_eur=100,
+ )
+ assert_matches_type(VehicleDetail, vehicle, path=["response"])
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ def test_method_create_with_all_params(self, client: Partnermax) -> None:
+ vehicle = client.dealers.vehicles.create(
+ dealer_id="dealer_id",
+ certified_km=0,
+ cost_price_eur=100,
+ motornet_code="xxxx",
+ plate="26F1KLZN",
+ registration_year=1960,
+ sale_price_eur=100,
+ alloy_wheel_size=13,
+ color="color",
+ description="description",
+ extended_warranty_enabled=True,
+ extended_warranty_months=1,
+ inspection_expiry_date=parse_date("2019-12-27"),
+ is_for_sale=True,
+ is_visible=True,
+ last_service_date=parse_date("2019-12-27"),
+ last_service_km=0,
+ last_service_notes="last_service_notes",
+ notes="notes",
+ previous_owner_count=0,
+ previous_ownership_transfer_date=parse_date("2019-12-27"),
+ registration_month=1,
+ road_tax_expiry_date=parse_date("2019-12-27"),
+ vat_displayed=True,
+ vehicle_damaged=True,
+ vin="PTNLCJPPNYGP316PJ",
+ idempotency_key="Idempotency-Key",
+ )
+ assert_matches_type(VehicleDetail, vehicle, path=["response"])
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ def test_raw_response_create(self, client: Partnermax) -> None:
+ response = client.dealers.vehicles.with_raw_response.create(
+ dealer_id="dealer_id",
+ certified_km=0,
+ cost_price_eur=100,
+ motornet_code="xxxx",
+ plate="26F1KLZN",
+ registration_year=1960,
+ sale_price_eur=100,
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ vehicle = response.parse()
+ assert_matches_type(VehicleDetail, vehicle, path=["response"])
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ def test_streaming_response_create(self, client: Partnermax) -> None:
+ with client.dealers.vehicles.with_streaming_response.create(
+ dealer_id="dealer_id",
+ certified_km=0,
+ cost_price_eur=100,
+ motornet_code="xxxx",
+ plate="26F1KLZN",
+ registration_year=1960,
+ sale_price_eur=100,
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ vehicle = response.parse()
+ assert_matches_type(VehicleDetail, vehicle, path=["response"])
+
+ assert cast(Any, response.is_closed) is True
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ def test_path_params_create(self, client: Partnermax) -> None:
+ with pytest.raises(ValueError, match=r"Expected a non-empty value for `dealer_id` but received ''"):
+ client.dealers.vehicles.with_raw_response.create(
+ dealer_id="",
+ certified_km=0,
+ cost_price_eur=100,
+ motornet_code="xxxx",
+ plate="26F1KLZN",
+ registration_year=1960,
+ sale_price_eur=100,
+ )
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ def test_method_retrieve(self, client: Partnermax) -> None:
+ vehicle = client.dealers.vehicles.retrieve(
+ vehicle_id="vehicle_id",
+ dealer_id="dealer_id",
+ )
+ assert_matches_type(VehicleDetail, vehicle, path=["response"])
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ def test_method_retrieve_with_all_params(self, client: Partnermax) -> None:
+ vehicle = client.dealers.vehicles.retrieve(
+ vehicle_id="vehicle_id",
+ dealer_id="dealer_id",
+ include_deleted=True,
+ )
+ assert_matches_type(VehicleDetail, vehicle, path=["response"])
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ def test_raw_response_retrieve(self, client: Partnermax) -> None:
+ response = client.dealers.vehicles.with_raw_response.retrieve(
+ vehicle_id="vehicle_id",
+ dealer_id="dealer_id",
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ vehicle = response.parse()
+ assert_matches_type(VehicleDetail, vehicle, path=["response"])
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ def test_streaming_response_retrieve(self, client: Partnermax) -> None:
+ with client.dealers.vehicles.with_streaming_response.retrieve(
+ vehicle_id="vehicle_id",
+ dealer_id="dealer_id",
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ vehicle = response.parse()
+ assert_matches_type(VehicleDetail, vehicle, path=["response"])
+
+ assert cast(Any, response.is_closed) is True
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ def test_path_params_retrieve(self, client: Partnermax) -> None:
+ with pytest.raises(ValueError, match=r"Expected a non-empty value for `dealer_id` but received ''"):
+ client.dealers.vehicles.with_raw_response.retrieve(
+ vehicle_id="vehicle_id",
+ dealer_id="",
+ )
+
+ with pytest.raises(ValueError, match=r"Expected a non-empty value for `vehicle_id` but received ''"):
+ client.dealers.vehicles.with_raw_response.retrieve(
+ vehicle_id="",
+ dealer_id="dealer_id",
+ )
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ def test_method_update(self, client: Partnermax) -> None:
+ vehicle = client.dealers.vehicles.update(
+ vehicle_id="vehicle_id",
+ dealer_id="dealer_id",
+ )
+ assert_matches_type(VehicleDetail, vehicle, path=["response"])
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ def test_method_update_with_all_params(self, client: Partnermax) -> None:
+ vehicle = client.dealers.vehicles.update(
+ vehicle_id="vehicle_id",
+ dealer_id="dealer_id",
+ alloy_wheel_size=13,
+ certified_km=0,
+ color="color",
+ cost_price_eur=100,
+ description="description",
+ extended_warranty_enabled=True,
+ extended_warranty_months=1,
+ inspection_expiry_date=parse_date("2019-12-27"),
+ is_for_sale=True,
+ is_visible=True,
+ last_service_date=parse_date("2019-12-27"),
+ last_service_km=0,
+ last_service_notes="last_service_notes",
+ notes="notes",
+ previous_owner_count=0,
+ previous_ownership_transfer_date=parse_date("2019-12-27"),
+ registration_month=1,
+ road_tax_expiry_date=parse_date("2019-12-27"),
+ sale_price_eur=100,
+ vat_displayed=True,
+ vehicle_damaged=True,
+ idempotency_key="Idempotency-Key",
+ )
+ assert_matches_type(VehicleDetail, vehicle, path=["response"])
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ def test_raw_response_update(self, client: Partnermax) -> None:
+ response = client.dealers.vehicles.with_raw_response.update(
+ vehicle_id="vehicle_id",
+ dealer_id="dealer_id",
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ vehicle = response.parse()
+ assert_matches_type(VehicleDetail, vehicle, path=["response"])
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ def test_streaming_response_update(self, client: Partnermax) -> None:
+ with client.dealers.vehicles.with_streaming_response.update(
+ vehicle_id="vehicle_id",
+ dealer_id="dealer_id",
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ vehicle = response.parse()
+ assert_matches_type(VehicleDetail, vehicle, path=["response"])
+
+ assert cast(Any, response.is_closed) is True
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ def test_path_params_update(self, client: Partnermax) -> None:
+ with pytest.raises(ValueError, match=r"Expected a non-empty value for `dealer_id` but received ''"):
+ client.dealers.vehicles.with_raw_response.update(
+ vehicle_id="vehicle_id",
+ dealer_id="",
+ )
+
+ with pytest.raises(ValueError, match=r"Expected a non-empty value for `vehicle_id` but received ''"):
+ client.dealers.vehicles.with_raw_response.update(
+ vehicle_id="",
+ dealer_id="dealer_id",
+ )
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ def test_method_list(self, client: Partnermax) -> None:
+ vehicle = client.dealers.vehicles.list(
+ dealer_id="dealer_id",
+ )
+ assert_matches_type(VehicleList, vehicle, path=["response"])
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ def test_method_list_with_all_params(self, client: Partnermax) -> None:
+ vehicle = client.dealers.vehicles.list(
+ dealer_id="dealer_id",
+ cursor="cursor",
+ include_deleted=True,
+ is_for_sale=True,
+ is_visible=True,
+ limit=1,
+ )
+ assert_matches_type(VehicleList, vehicle, path=["response"])
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ def test_raw_response_list(self, client: Partnermax) -> None:
+ response = client.dealers.vehicles.with_raw_response.list(
+ dealer_id="dealer_id",
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ vehicle = response.parse()
+ assert_matches_type(VehicleList, vehicle, path=["response"])
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ def test_streaming_response_list(self, client: Partnermax) -> None:
+ with client.dealers.vehicles.with_streaming_response.list(
+ dealer_id="dealer_id",
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ vehicle = response.parse()
+ assert_matches_type(VehicleList, vehicle, path=["response"])
+
+ assert cast(Any, response.is_closed) is True
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ def test_path_params_list(self, client: Partnermax) -> None:
+ with pytest.raises(ValueError, match=r"Expected a non-empty value for `dealer_id` but received ''"):
+ client.dealers.vehicles.with_raw_response.list(
+ dealer_id="",
+ )
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ def test_method_delete(self, client: Partnermax) -> None:
+ vehicle = client.dealers.vehicles.delete(
+ vehicle_id="vehicle_id",
+ dealer_id="dealer_id",
+ )
+ assert vehicle is None
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ def test_raw_response_delete(self, client: Partnermax) -> None:
+ response = client.dealers.vehicles.with_raw_response.delete(
+ vehicle_id="vehicle_id",
+ dealer_id="dealer_id",
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ vehicle = response.parse()
+ assert vehicle is None
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ def test_streaming_response_delete(self, client: Partnermax) -> None:
+ with client.dealers.vehicles.with_streaming_response.delete(
+ vehicle_id="vehicle_id",
+ dealer_id="dealer_id",
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ vehicle = response.parse()
+ assert vehicle is None
+
+ assert cast(Any, response.is_closed) is True
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ def test_path_params_delete(self, client: Partnermax) -> None:
+ with pytest.raises(ValueError, match=r"Expected a non-empty value for `dealer_id` but received ''"):
+ client.dealers.vehicles.with_raw_response.delete(
+ vehicle_id="vehicle_id",
+ dealer_id="",
+ )
+
+ with pytest.raises(ValueError, match=r"Expected a non-empty value for `vehicle_id` but received ''"):
+ client.dealers.vehicles.with_raw_response.delete(
+ vehicle_id="",
+ dealer_id="dealer_id",
+ )
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ def test_method_bulk(self, client: Partnermax) -> None:
+ vehicle = client.dealers.vehicles.bulk(
+ dealer_id="dealer_id",
+ vehicles=[
+ {
+ "certified_km": 0,
+ "cost_price_eur": 100,
+ "motornet_code": "xxxx",
+ "plate": "26F1KLZN",
+ "registration_year": 1960,
+ "sale_price_eur": 100,
+ }
+ ],
+ )
+ assert_matches_type(BulkCreateVehiclesResponse, vehicle, path=["response"])
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ def test_method_bulk_with_all_params(self, client: Partnermax) -> None:
+ vehicle = client.dealers.vehicles.bulk(
+ dealer_id="dealer_id",
+ vehicles=[
+ {
+ "certified_km": 0,
+ "cost_price_eur": 100,
+ "motornet_code": "xxxx",
+ "plate": "26F1KLZN",
+ "registration_year": 1960,
+ "sale_price_eur": 100,
+ "alloy_wheel_size": 13,
+ "color": "color",
+ "description": "description",
+ "extended_warranty_enabled": True,
+ "extended_warranty_months": 1,
+ "inspection_expiry_date": parse_date("2019-12-27"),
+ "is_for_sale": True,
+ "is_visible": True,
+ "last_service_date": parse_date("2019-12-27"),
+ "last_service_km": 0,
+ "last_service_notes": "last_service_notes",
+ "notes": "notes",
+ "previous_owner_count": 0,
+ "previous_ownership_transfer_date": parse_date("2019-12-27"),
+ "registration_month": 1,
+ "road_tax_expiry_date": parse_date("2019-12-27"),
+ "vat_displayed": True,
+ "vehicle_damaged": True,
+ "vin": "PTNLCJPPNYGP316PJ",
+ }
+ ],
+ idempotency_key="Idempotency-Key",
+ )
+ assert_matches_type(BulkCreateVehiclesResponse, vehicle, path=["response"])
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ def test_raw_response_bulk(self, client: Partnermax) -> None:
+ response = client.dealers.vehicles.with_raw_response.bulk(
+ dealer_id="dealer_id",
+ vehicles=[
+ {
+ "certified_km": 0,
+ "cost_price_eur": 100,
+ "motornet_code": "xxxx",
+ "plate": "26F1KLZN",
+ "registration_year": 1960,
+ "sale_price_eur": 100,
+ }
+ ],
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ vehicle = response.parse()
+ assert_matches_type(BulkCreateVehiclesResponse, vehicle, path=["response"])
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ def test_streaming_response_bulk(self, client: Partnermax) -> None:
+ with client.dealers.vehicles.with_streaming_response.bulk(
+ dealer_id="dealer_id",
+ vehicles=[
+ {
+ "certified_km": 0,
+ "cost_price_eur": 100,
+ "motornet_code": "xxxx",
+ "plate": "26F1KLZN",
+ "registration_year": 1960,
+ "sale_price_eur": 100,
+ }
+ ],
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ vehicle = response.parse()
+ assert_matches_type(BulkCreateVehiclesResponse, vehicle, path=["response"])
+
+ assert cast(Any, response.is_closed) is True
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ def test_path_params_bulk(self, client: Partnermax) -> None:
+ with pytest.raises(ValueError, match=r"Expected a non-empty value for `dealer_id` but received ''"):
+ client.dealers.vehicles.with_raw_response.bulk(
+ dealer_id="",
+ vehicles=[
+ {
+ "certified_km": 0,
+ "cost_price_eur": 100,
+ "motornet_code": "xxxx",
+ "plate": "26F1KLZN",
+ "registration_year": 1960,
+ "sale_price_eur": 100,
+ }
+ ],
+ )
+
+
+class TestAsyncVehicles:
+ parametrize = pytest.mark.parametrize(
+ "async_client", [False, True, {"http_client": "aiohttp"}], indirect=True, ids=["loose", "strict", "aiohttp"]
+ )
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ async def test_method_create(self, async_client: AsyncPartnermax) -> None:
+ vehicle = await async_client.dealers.vehicles.create(
+ dealer_id="dealer_id",
+ certified_km=0,
+ cost_price_eur=100,
+ motornet_code="xxxx",
+ plate="26F1KLZN",
+ registration_year=1960,
+ sale_price_eur=100,
+ )
+ assert_matches_type(VehicleDetail, vehicle, path=["response"])
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ async def test_method_create_with_all_params(self, async_client: AsyncPartnermax) -> None:
+ vehicle = await async_client.dealers.vehicles.create(
+ dealer_id="dealer_id",
+ certified_km=0,
+ cost_price_eur=100,
+ motornet_code="xxxx",
+ plate="26F1KLZN",
+ registration_year=1960,
+ sale_price_eur=100,
+ alloy_wheel_size=13,
+ color="color",
+ description="description",
+ extended_warranty_enabled=True,
+ extended_warranty_months=1,
+ inspection_expiry_date=parse_date("2019-12-27"),
+ is_for_sale=True,
+ is_visible=True,
+ last_service_date=parse_date("2019-12-27"),
+ last_service_km=0,
+ last_service_notes="last_service_notes",
+ notes="notes",
+ previous_owner_count=0,
+ previous_ownership_transfer_date=parse_date("2019-12-27"),
+ registration_month=1,
+ road_tax_expiry_date=parse_date("2019-12-27"),
+ vat_displayed=True,
+ vehicle_damaged=True,
+ vin="PTNLCJPPNYGP316PJ",
+ idempotency_key="Idempotency-Key",
+ )
+ assert_matches_type(VehicleDetail, vehicle, path=["response"])
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ async def test_raw_response_create(self, async_client: AsyncPartnermax) -> None:
+ response = await async_client.dealers.vehicles.with_raw_response.create(
+ dealer_id="dealer_id",
+ certified_km=0,
+ cost_price_eur=100,
+ motornet_code="xxxx",
+ plate="26F1KLZN",
+ registration_year=1960,
+ sale_price_eur=100,
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ vehicle = await response.parse()
+ assert_matches_type(VehicleDetail, vehicle, path=["response"])
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ async def test_streaming_response_create(self, async_client: AsyncPartnermax) -> None:
+ async with async_client.dealers.vehicles.with_streaming_response.create(
+ dealer_id="dealer_id",
+ certified_km=0,
+ cost_price_eur=100,
+ motornet_code="xxxx",
+ plate="26F1KLZN",
+ registration_year=1960,
+ sale_price_eur=100,
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ vehicle = await response.parse()
+ assert_matches_type(VehicleDetail, vehicle, path=["response"])
+
+ assert cast(Any, response.is_closed) is True
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ async def test_path_params_create(self, async_client: AsyncPartnermax) -> None:
+ with pytest.raises(ValueError, match=r"Expected a non-empty value for `dealer_id` but received ''"):
+ await async_client.dealers.vehicles.with_raw_response.create(
+ dealer_id="",
+ certified_km=0,
+ cost_price_eur=100,
+ motornet_code="xxxx",
+ plate="26F1KLZN",
+ registration_year=1960,
+ sale_price_eur=100,
+ )
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ async def test_method_retrieve(self, async_client: AsyncPartnermax) -> None:
+ vehicle = await async_client.dealers.vehicles.retrieve(
+ vehicle_id="vehicle_id",
+ dealer_id="dealer_id",
+ )
+ assert_matches_type(VehicleDetail, vehicle, path=["response"])
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ async def test_method_retrieve_with_all_params(self, async_client: AsyncPartnermax) -> None:
+ vehicle = await async_client.dealers.vehicles.retrieve(
+ vehicle_id="vehicle_id",
+ dealer_id="dealer_id",
+ include_deleted=True,
+ )
+ assert_matches_type(VehicleDetail, vehicle, path=["response"])
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ async def test_raw_response_retrieve(self, async_client: AsyncPartnermax) -> None:
+ response = await async_client.dealers.vehicles.with_raw_response.retrieve(
+ vehicle_id="vehicle_id",
+ dealer_id="dealer_id",
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ vehicle = await response.parse()
+ assert_matches_type(VehicleDetail, vehicle, path=["response"])
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ async def test_streaming_response_retrieve(self, async_client: AsyncPartnermax) -> None:
+ async with async_client.dealers.vehicles.with_streaming_response.retrieve(
+ vehicle_id="vehicle_id",
+ dealer_id="dealer_id",
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ vehicle = await response.parse()
+ assert_matches_type(VehicleDetail, vehicle, path=["response"])
+
+ assert cast(Any, response.is_closed) is True
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ async def test_path_params_retrieve(self, async_client: AsyncPartnermax) -> None:
+ with pytest.raises(ValueError, match=r"Expected a non-empty value for `dealer_id` but received ''"):
+ await async_client.dealers.vehicles.with_raw_response.retrieve(
+ vehicle_id="vehicle_id",
+ dealer_id="",
+ )
+
+ with pytest.raises(ValueError, match=r"Expected a non-empty value for `vehicle_id` but received ''"):
+ await async_client.dealers.vehicles.with_raw_response.retrieve(
+ vehicle_id="",
+ dealer_id="dealer_id",
+ )
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ async def test_method_update(self, async_client: AsyncPartnermax) -> None:
+ vehicle = await async_client.dealers.vehicles.update(
+ vehicle_id="vehicle_id",
+ dealer_id="dealer_id",
+ )
+ assert_matches_type(VehicleDetail, vehicle, path=["response"])
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ async def test_method_update_with_all_params(self, async_client: AsyncPartnermax) -> None:
+ vehicle = await async_client.dealers.vehicles.update(
+ vehicle_id="vehicle_id",
+ dealer_id="dealer_id",
+ alloy_wheel_size=13,
+ certified_km=0,
+ color="color",
+ cost_price_eur=100,
+ description="description",
+ extended_warranty_enabled=True,
+ extended_warranty_months=1,
+ inspection_expiry_date=parse_date("2019-12-27"),
+ is_for_sale=True,
+ is_visible=True,
+ last_service_date=parse_date("2019-12-27"),
+ last_service_km=0,
+ last_service_notes="last_service_notes",
+ notes="notes",
+ previous_owner_count=0,
+ previous_ownership_transfer_date=parse_date("2019-12-27"),
+ registration_month=1,
+ road_tax_expiry_date=parse_date("2019-12-27"),
+ sale_price_eur=100,
+ vat_displayed=True,
+ vehicle_damaged=True,
+ idempotency_key="Idempotency-Key",
+ )
+ assert_matches_type(VehicleDetail, vehicle, path=["response"])
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ async def test_raw_response_update(self, async_client: AsyncPartnermax) -> None:
+ response = await async_client.dealers.vehicles.with_raw_response.update(
+ vehicle_id="vehicle_id",
+ dealer_id="dealer_id",
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ vehicle = await response.parse()
+ assert_matches_type(VehicleDetail, vehicle, path=["response"])
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ async def test_streaming_response_update(self, async_client: AsyncPartnermax) -> None:
+ async with async_client.dealers.vehicles.with_streaming_response.update(
+ vehicle_id="vehicle_id",
+ dealer_id="dealer_id",
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ vehicle = await response.parse()
+ assert_matches_type(VehicleDetail, vehicle, path=["response"])
+
+ assert cast(Any, response.is_closed) is True
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ async def test_path_params_update(self, async_client: AsyncPartnermax) -> None:
+ with pytest.raises(ValueError, match=r"Expected a non-empty value for `dealer_id` but received ''"):
+ await async_client.dealers.vehicles.with_raw_response.update(
+ vehicle_id="vehicle_id",
+ dealer_id="",
+ )
+
+ with pytest.raises(ValueError, match=r"Expected a non-empty value for `vehicle_id` but received ''"):
+ await async_client.dealers.vehicles.with_raw_response.update(
+ vehicle_id="",
+ dealer_id="dealer_id",
+ )
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ async def test_method_list(self, async_client: AsyncPartnermax) -> None:
+ vehicle = await async_client.dealers.vehicles.list(
+ dealer_id="dealer_id",
+ )
+ assert_matches_type(VehicleList, vehicle, path=["response"])
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ async def test_method_list_with_all_params(self, async_client: AsyncPartnermax) -> None:
+ vehicle = await async_client.dealers.vehicles.list(
+ dealer_id="dealer_id",
+ cursor="cursor",
+ include_deleted=True,
+ is_for_sale=True,
+ is_visible=True,
+ limit=1,
+ )
+ assert_matches_type(VehicleList, vehicle, path=["response"])
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ async def test_raw_response_list(self, async_client: AsyncPartnermax) -> None:
+ response = await async_client.dealers.vehicles.with_raw_response.list(
+ dealer_id="dealer_id",
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ vehicle = await response.parse()
+ assert_matches_type(VehicleList, vehicle, path=["response"])
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ async def test_streaming_response_list(self, async_client: AsyncPartnermax) -> None:
+ async with async_client.dealers.vehicles.with_streaming_response.list(
+ dealer_id="dealer_id",
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ vehicle = await response.parse()
+ assert_matches_type(VehicleList, vehicle, path=["response"])
+
+ assert cast(Any, response.is_closed) is True
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ async def test_path_params_list(self, async_client: AsyncPartnermax) -> None:
+ with pytest.raises(ValueError, match=r"Expected a non-empty value for `dealer_id` but received ''"):
+ await async_client.dealers.vehicles.with_raw_response.list(
+ dealer_id="",
+ )
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ async def test_method_delete(self, async_client: AsyncPartnermax) -> None:
+ vehicle = await async_client.dealers.vehicles.delete(
+ vehicle_id="vehicle_id",
+ dealer_id="dealer_id",
+ )
+ assert vehicle is None
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ async def test_raw_response_delete(self, async_client: AsyncPartnermax) -> None:
+ response = await async_client.dealers.vehicles.with_raw_response.delete(
+ vehicle_id="vehicle_id",
+ dealer_id="dealer_id",
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ vehicle = await response.parse()
+ assert vehicle is None
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ async def test_streaming_response_delete(self, async_client: AsyncPartnermax) -> None:
+ async with async_client.dealers.vehicles.with_streaming_response.delete(
+ vehicle_id="vehicle_id",
+ dealer_id="dealer_id",
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ vehicle = await response.parse()
+ assert vehicle is None
+
+ assert cast(Any, response.is_closed) is True
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ async def test_path_params_delete(self, async_client: AsyncPartnermax) -> None:
+ with pytest.raises(ValueError, match=r"Expected a non-empty value for `dealer_id` but received ''"):
+ await async_client.dealers.vehicles.with_raw_response.delete(
+ vehicle_id="vehicle_id",
+ dealer_id="",
+ )
+
+ with pytest.raises(ValueError, match=r"Expected a non-empty value for `vehicle_id` but received ''"):
+ await async_client.dealers.vehicles.with_raw_response.delete(
+ vehicle_id="",
+ dealer_id="dealer_id",
+ )
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ async def test_method_bulk(self, async_client: AsyncPartnermax) -> None:
+ vehicle = await async_client.dealers.vehicles.bulk(
+ dealer_id="dealer_id",
+ vehicles=[
+ {
+ "certified_km": 0,
+ "cost_price_eur": 100,
+ "motornet_code": "xxxx",
+ "plate": "26F1KLZN",
+ "registration_year": 1960,
+ "sale_price_eur": 100,
+ }
+ ],
+ )
+ assert_matches_type(BulkCreateVehiclesResponse, vehicle, path=["response"])
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ async def test_method_bulk_with_all_params(self, async_client: AsyncPartnermax) -> None:
+ vehicle = await async_client.dealers.vehicles.bulk(
+ dealer_id="dealer_id",
+ vehicles=[
+ {
+ "certified_km": 0,
+ "cost_price_eur": 100,
+ "motornet_code": "xxxx",
+ "plate": "26F1KLZN",
+ "registration_year": 1960,
+ "sale_price_eur": 100,
+ "alloy_wheel_size": 13,
+ "color": "color",
+ "description": "description",
+ "extended_warranty_enabled": True,
+ "extended_warranty_months": 1,
+ "inspection_expiry_date": parse_date("2019-12-27"),
+ "is_for_sale": True,
+ "is_visible": True,
+ "last_service_date": parse_date("2019-12-27"),
+ "last_service_km": 0,
+ "last_service_notes": "last_service_notes",
+ "notes": "notes",
+ "previous_owner_count": 0,
+ "previous_ownership_transfer_date": parse_date("2019-12-27"),
+ "registration_month": 1,
+ "road_tax_expiry_date": parse_date("2019-12-27"),
+ "vat_displayed": True,
+ "vehicle_damaged": True,
+ "vin": "PTNLCJPPNYGP316PJ",
+ }
+ ],
+ idempotency_key="Idempotency-Key",
+ )
+ assert_matches_type(BulkCreateVehiclesResponse, vehicle, path=["response"])
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ async def test_raw_response_bulk(self, async_client: AsyncPartnermax) -> None:
+ response = await async_client.dealers.vehicles.with_raw_response.bulk(
+ dealer_id="dealer_id",
+ vehicles=[
+ {
+ "certified_km": 0,
+ "cost_price_eur": 100,
+ "motornet_code": "xxxx",
+ "plate": "26F1KLZN",
+ "registration_year": 1960,
+ "sale_price_eur": 100,
+ }
+ ],
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ vehicle = await response.parse()
+ assert_matches_type(BulkCreateVehiclesResponse, vehicle, path=["response"])
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ async def test_streaming_response_bulk(self, async_client: AsyncPartnermax) -> None:
+ async with async_client.dealers.vehicles.with_streaming_response.bulk(
+ dealer_id="dealer_id",
+ vehicles=[
+ {
+ "certified_km": 0,
+ "cost_price_eur": 100,
+ "motornet_code": "xxxx",
+ "plate": "26F1KLZN",
+ "registration_year": 1960,
+ "sale_price_eur": 100,
+ }
+ ],
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ vehicle = await response.parse()
+ assert_matches_type(BulkCreateVehiclesResponse, vehicle, path=["response"])
+
+ assert cast(Any, response.is_closed) is True
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ async def test_path_params_bulk(self, async_client: AsyncPartnermax) -> None:
+ with pytest.raises(ValueError, match=r"Expected a non-empty value for `dealer_id` but received ''"):
+ await async_client.dealers.vehicles.with_raw_response.bulk(
+ dealer_id="",
+ vehicles=[
+ {
+ "certified_km": 0,
+ "cost_price_eur": 100,
+ "motornet_code": "xxxx",
+ "plate": "26F1KLZN",
+ "registration_year": 1960,
+ "sale_price_eur": 100,
+ }
+ ],
+ )
diff --git a/tests/api_resources/test_dealers.py b/tests/api_resources/test_dealers.py
index 2fb2532..dd5d3fa 100644
--- a/tests/api_resources/test_dealers.py
+++ b/tests/api_resources/test_dealers.py
@@ -24,12 +24,15 @@ class TestDealers:
@parametrize
def test_method_create(self, client: Partnermax) -> None:
dealer = client.dealers.create(
- business_name="Rossi Automobili S.R.L.",
- contact_email="info@rossi-auto.it",
- postal_code="20121",
- primary_domain="rossi-auto.it",
- province_code="MI",
- vat_number="IT01234567890",
+ address="xx",
+ business_name="xx",
+ city="xx",
+ contact_email="dev@stainless.com",
+ contact_phone="xxxxx",
+ postal_code="21029",
+ primary_domain="29-0.mi-57.16u-2d91-aha.o-l7c19m0.z.9zhin-8ja2-7.447----86.6.61--5-6.2.3-i2al.r.name",
+ province_code="SE",
+ vat_number="IT21029798095",
)
assert_matches_type(DealerDetail, dealer, path=["response"])
@@ -37,16 +40,18 @@ def test_method_create(self, client: Partnermax) -> None:
@parametrize
def test_method_create_with_all_params(self, client: Partnermax) -> None:
dealer = client.dealers.create(
- business_name="Rossi Automobili S.R.L.",
- contact_email="info@rossi-auto.it",
- postal_code="20121",
- primary_domain="rossi-auto.it",
- province_code="MI",
- vat_number="IT01234567890",
+ address="xx",
+ business_name="xx",
+ city="xx",
+ contact_email="dev@stainless.com",
+ contact_phone="xxxxx",
+ postal_code="21029",
+ primary_domain="29-0.mi-57.16u-2d91-aha.o-l7c19m0.z.9zhin-8ja2-7.447----86.6.61--5-6.2.3-i2al.r.name",
+ province_code="SE",
+ vat_number="IT21029798095",
activate=True,
- contact_phone="+390212345678",
- metadata={"partner_internal_id": "DLR-9182"},
- idempotency_key="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+ metadata={"foo": "string"},
+ idempotency_key="Idempotency-Key",
)
assert_matches_type(DealerDetail, dealer, path=["response"])
@@ -54,12 +59,15 @@ def test_method_create_with_all_params(self, client: Partnermax) -> None:
@parametrize
def test_raw_response_create(self, client: Partnermax) -> None:
response = client.dealers.with_raw_response.create(
- business_name="Rossi Automobili S.R.L.",
- contact_email="info@rossi-auto.it",
- postal_code="20121",
- primary_domain="rossi-auto.it",
- province_code="MI",
- vat_number="IT01234567890",
+ address="xx",
+ business_name="xx",
+ city="xx",
+ contact_email="dev@stainless.com",
+ contact_phone="xxxxx",
+ postal_code="21029",
+ primary_domain="29-0.mi-57.16u-2d91-aha.o-l7c19m0.z.9zhin-8ja2-7.447----86.6.61--5-6.2.3-i2al.r.name",
+ province_code="SE",
+ vat_number="IT21029798095",
)
assert response.is_closed is True
@@ -71,12 +79,15 @@ def test_raw_response_create(self, client: Partnermax) -> None:
@parametrize
def test_streaming_response_create(self, client: Partnermax) -> None:
with client.dealers.with_streaming_response.create(
- business_name="Rossi Automobili S.R.L.",
- contact_email="info@rossi-auto.it",
- postal_code="20121",
- primary_domain="rossi-auto.it",
- province_code="MI",
- vat_number="IT01234567890",
+ address="xx",
+ business_name="xx",
+ city="xx",
+ contact_email="dev@stainless.com",
+ contact_phone="xxxxx",
+ postal_code="21029",
+ primary_domain="29-0.mi-57.16u-2d91-aha.o-l7c19m0.z.9zhin-8ja2-7.447----86.6.61--5-6.2.3-i2al.r.name",
+ province_code="SE",
+ vat_number="IT21029798095",
) as response:
assert not response.is_closed
assert response.http_request.headers.get("X-Stainless-Lang") == "python"
@@ -141,14 +152,16 @@ def test_method_update(self, client: Partnermax) -> None:
def test_method_update_with_all_params(self, client: Partnermax) -> None:
dealer = client.dealers.update(
dealer_id="dealer_id",
- business_name="business_name",
+ address="xx",
+ business_name="xx",
+ city="xx",
contact_email="dev@stainless.com",
- contact_phone="contact_phone",
+ contact_phone="xxxxx",
metadata={"foo": "string"},
postal_code="21029",
province_code="SE",
status="active",
- idempotency_key="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+ idempotency_key="Idempotency-Key",
)
assert_matches_type(DealerDetail, dealer, path=["response"])
@@ -276,12 +289,15 @@ class TestAsyncDealers:
@parametrize
async def test_method_create(self, async_client: AsyncPartnermax) -> None:
dealer = await async_client.dealers.create(
- business_name="Rossi Automobili S.R.L.",
- contact_email="info@rossi-auto.it",
- postal_code="20121",
- primary_domain="rossi-auto.it",
- province_code="MI",
- vat_number="IT01234567890",
+ address="xx",
+ business_name="xx",
+ city="xx",
+ contact_email="dev@stainless.com",
+ contact_phone="xxxxx",
+ postal_code="21029",
+ primary_domain="29-0.mi-57.16u-2d91-aha.o-l7c19m0.z.9zhin-8ja2-7.447----86.6.61--5-6.2.3-i2al.r.name",
+ province_code="SE",
+ vat_number="IT21029798095",
)
assert_matches_type(DealerDetail, dealer, path=["response"])
@@ -289,16 +305,18 @@ async def test_method_create(self, async_client: AsyncPartnermax) -> None:
@parametrize
async def test_method_create_with_all_params(self, async_client: AsyncPartnermax) -> None:
dealer = await async_client.dealers.create(
- business_name="Rossi Automobili S.R.L.",
- contact_email="info@rossi-auto.it",
- postal_code="20121",
- primary_domain="rossi-auto.it",
- province_code="MI",
- vat_number="IT01234567890",
+ address="xx",
+ business_name="xx",
+ city="xx",
+ contact_email="dev@stainless.com",
+ contact_phone="xxxxx",
+ postal_code="21029",
+ primary_domain="29-0.mi-57.16u-2d91-aha.o-l7c19m0.z.9zhin-8ja2-7.447----86.6.61--5-6.2.3-i2al.r.name",
+ province_code="SE",
+ vat_number="IT21029798095",
activate=True,
- contact_phone="+390212345678",
- metadata={"partner_internal_id": "DLR-9182"},
- idempotency_key="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+ metadata={"foo": "string"},
+ idempotency_key="Idempotency-Key",
)
assert_matches_type(DealerDetail, dealer, path=["response"])
@@ -306,12 +324,15 @@ async def test_method_create_with_all_params(self, async_client: AsyncPartnermax
@parametrize
async def test_raw_response_create(self, async_client: AsyncPartnermax) -> None:
response = await async_client.dealers.with_raw_response.create(
- business_name="Rossi Automobili S.R.L.",
- contact_email="info@rossi-auto.it",
- postal_code="20121",
- primary_domain="rossi-auto.it",
- province_code="MI",
- vat_number="IT01234567890",
+ address="xx",
+ business_name="xx",
+ city="xx",
+ contact_email="dev@stainless.com",
+ contact_phone="xxxxx",
+ postal_code="21029",
+ primary_domain="29-0.mi-57.16u-2d91-aha.o-l7c19m0.z.9zhin-8ja2-7.447----86.6.61--5-6.2.3-i2al.r.name",
+ province_code="SE",
+ vat_number="IT21029798095",
)
assert response.is_closed is True
@@ -323,12 +344,15 @@ async def test_raw_response_create(self, async_client: AsyncPartnermax) -> None:
@parametrize
async def test_streaming_response_create(self, async_client: AsyncPartnermax) -> None:
async with async_client.dealers.with_streaming_response.create(
- business_name="Rossi Automobili S.R.L.",
- contact_email="info@rossi-auto.it",
- postal_code="20121",
- primary_domain="rossi-auto.it",
- province_code="MI",
- vat_number="IT01234567890",
+ address="xx",
+ business_name="xx",
+ city="xx",
+ contact_email="dev@stainless.com",
+ contact_phone="xxxxx",
+ postal_code="21029",
+ primary_domain="29-0.mi-57.16u-2d91-aha.o-l7c19m0.z.9zhin-8ja2-7.447----86.6.61--5-6.2.3-i2al.r.name",
+ province_code="SE",
+ vat_number="IT21029798095",
) as response:
assert not response.is_closed
assert response.http_request.headers.get("X-Stainless-Lang") == "python"
@@ -393,14 +417,16 @@ async def test_method_update(self, async_client: AsyncPartnermax) -> None:
async def test_method_update_with_all_params(self, async_client: AsyncPartnermax) -> None:
dealer = await async_client.dealers.update(
dealer_id="dealer_id",
- business_name="business_name",
+ address="xx",
+ business_name="xx",
+ city="xx",
contact_email="dev@stainless.com",
- contact_phone="contact_phone",
+ contact_phone="xxxxx",
metadata={"foo": "string"},
postal_code="21029",
province_code="SE",
status="active",
- idempotency_key="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+ idempotency_key="Idempotency-Key",
)
assert_matches_type(DealerDetail, dealer, path=["response"])
diff --git a/tests/api_resources/test_keys.py b/tests/api_resources/test_keys.py
index 96f2897..482b3f1 100644
--- a/tests/api_resources/test_keys.py
+++ b/tests/api_resources/test_keys.py
@@ -50,7 +50,7 @@ def test_streaming_response_list(self, client: Partnermax) -> None:
@parametrize
def test_method_issue(self, client: Partnermax) -> None:
key = client.keys.issue(
- label="production-backend-2026",
+ label="x",
)
assert_matches_type(KeyIssueResponse, key, path=["response"])
@@ -58,9 +58,9 @@ def test_method_issue(self, client: Partnermax) -> None:
@parametrize
def test_method_issue_with_all_params(self, client: Partnermax) -> None:
key = client.keys.issue(
- label="production-backend-2026",
+ label="x",
expires_at=parse_datetime("2019-12-27T18:11:19.117Z"),
- idempotency_key="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+ idempotency_key="Idempotency-Key",
)
assert_matches_type(KeyIssueResponse, key, path=["response"])
@@ -68,7 +68,7 @@ def test_method_issue_with_all_params(self, client: Partnermax) -> None:
@parametrize
def test_raw_response_issue(self, client: Partnermax) -> None:
response = client.keys.with_raw_response.issue(
- label="production-backend-2026",
+ label="x",
)
assert response.is_closed is True
@@ -80,7 +80,7 @@ def test_raw_response_issue(self, client: Partnermax) -> None:
@parametrize
def test_streaming_response_issue(self, client: Partnermax) -> None:
with client.keys.with_streaming_response.issue(
- label="production-backend-2026",
+ label="x",
) as response:
assert not response.is_closed
assert response.http_request.headers.get("X-Stainless-Lang") == "python"
@@ -170,7 +170,7 @@ async def test_streaming_response_list(self, async_client: AsyncPartnermax) -> N
@parametrize
async def test_method_issue(self, async_client: AsyncPartnermax) -> None:
key = await async_client.keys.issue(
- label="production-backend-2026",
+ label="x",
)
assert_matches_type(KeyIssueResponse, key, path=["response"])
@@ -178,9 +178,9 @@ async def test_method_issue(self, async_client: AsyncPartnermax) -> None:
@parametrize
async def test_method_issue_with_all_params(self, async_client: AsyncPartnermax) -> None:
key = await async_client.keys.issue(
- label="production-backend-2026",
+ label="x",
expires_at=parse_datetime("2019-12-27T18:11:19.117Z"),
- idempotency_key="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+ idempotency_key="Idempotency-Key",
)
assert_matches_type(KeyIssueResponse, key, path=["response"])
@@ -188,7 +188,7 @@ async def test_method_issue_with_all_params(self, async_client: AsyncPartnermax)
@parametrize
async def test_raw_response_issue(self, async_client: AsyncPartnermax) -> None:
response = await async_client.keys.with_raw_response.issue(
- label="production-backend-2026",
+ label="x",
)
assert response.is_closed is True
@@ -200,7 +200,7 @@ async def test_raw_response_issue(self, async_client: AsyncPartnermax) -> None:
@parametrize
async def test_streaming_response_issue(self, async_client: AsyncPartnermax) -> None:
async with async_client.keys.with_streaming_response.issue(
- label="production-backend-2026",
+ label="x",
) as response:
assert not response.is_closed
assert response.http_request.headers.get("X-Stainless-Lang") == "python"