Integration recipes

These recipes translate the published OpenAPI 1.71.0 contract into implementable integration sequences. The OpenAPI document remains authoritative for every schema, enum, status and error code.

Use the server URL https://api.tapinomahub.com and append the paths shown below. Send Accept: application/json and keep X-Api-Key in a server-side secret store. A sandbox key uses the same server URL and follows the documented sandbox behaviour.

Rules shared by every recipe

Tenant and concurrency rules

Error and retry rules

Idempotency rules

Asynchronous rules

Storage and retention

Operation-specific expiry and refresh behaviour does not grant a general right to retain request or response data.

Storage and retention: The public contract does not grant a general right to retain submitted content or returned data. The integrating system must apply an approved retention policy, minimise personal data and copy an output asset only when that policy permits it.

Recipe 1: ERP vehicle, parts and cart integration

Goal and non-goals

Goal: enrich an ERP vehicle record, retrieve the correctly characterised parts set and optionally check one to 30 ERP cart positions against a VIN.

Non-goals: creating orders, changing stock, proving installation compatibility from a type-level or incomplete result, or treating tapinomahub content as the ERP's accounting record.

Prerequisites

System-of-record boundary

The ERP remains authoritative for customers, the individual physical vehicle, work orders, cart lines, quantities, stock, negotiated prices and accounting. The tapinomahub API supplies derived vehicle, parts and comparison data. Subject to the approved data policy, keep the VIN and ERP vehicle ID distinct from tapiId: tapiId identifies a technical vehicle type and several VINs may share it.

Operation sequence

  1. Optionally verify connectivity with GET /ping (getSystemStatus). Do not send Idempotency-Key to this read operation.
  2. Establish vehicle context with exactly one of these entry branches:
    • Direct server-to-server branch: call GET /vin/{vin}/vehicle (getVehicleByVin) with explicit provider=2 or provider=3, the two-letter country, and explicit choices for the optional includeEquipments, includeColors and includeTechnical flags, or deliberately accept their true defaults. Carry forward the returned vin, tapiId, provider and incomplete values.
    • Browser-assisted branch: server-side, call POST /vin/redirect-sessions (createVinRedirectSession) with a 3–17-character vin input, HTTPS returnUrl and an opaque state. Use an Idempotency-Key. Redirect only the user agent to the returned redirectUrl before expiresAt. On the callback, require the original state and, as a local anti-forgery control, reject a state already consumed by the ERP; accept tapiId only when status=completed. A cancellation has no tapiId. The callback does not contain the VIN completed in the browser. Fetch technical type data with GET /vehicles/{tapiId} (getVehicleByTapiId), but treat an original 3–16-character input as a terminal boundary for later VIN-dependent calls until a full VIN is obtained independently.
  3. Only with a known full 17-character VIN, retrieve parts through route 1 with GET /vin/{vin}/parts (getPartsByVin). Omit the optional provider field unless the integration explicitly needs it; if it is sent, use the documented value consistently.
    • On 200, consume vin, tapiId, provider, matchLevel, allCategorySigns, missingCategories and parts; every part contains required tapiGenArt and vdi fields, and vdi=[] is valid when no mapping is available.
    • On 202, persist jobId, statusUrl, vin, provider, createdAt and retry timing, then poll GET /vin/parts/jobs/{jobId} (getVinPartsJob).
  4. If the ERP needs cart verification, call POST /vin/cart-check (checkVinCart) with {vin, country?, mode, oeNumbers} and an Idempotency-Key. mode is exactly type or vehicle; preserve request order and duplicates.
    • On 200, map results back to cart lines by array position and keep complete with every result.
    • On 202, persist the returned cart-check jobId and statusUrl, then poll only GET /vin/cart-check/jobs/{jobId} (getVinCartCheckJob).
  5. Optionally read monthly usage with GET /client/usage (getClientUsage) using the same tenant key. This is monitoring data, not an ERP financial posting.

Identifiers carried forward

Identifier Source Destination and rule
ERP vehicle ID ERP Local correlation only; never sent as a substitute for VIN or tapiId.
Full vin ERP input or an independently confirmed response Vehicle, parts and cart operations. A browser callback returns no completed VIN, so its original partial input cannot be promoted to this field.
state ERP Redirect callback correlation echoed unchanged by the API; the ERP verifies it and enforces one-time use locally before accepting tapiId.
tapiId Vehicle flow or redirect callback getVehicleByTapiId and later vehicle-listing calls; store as a type-level reference.
provider Configured request and tapinomahub response Keep the returned numeric value when the approved policy permits it and use only values documented for the selected operation.
jobId / statusUrl 202 response Only the matching documented polling operation. Do not treat it as an ERP job ID.
OE position index ERP cart Response results preserves order and duplicates; map by position, not by deduplicated OE number.

Validation, errors, idempotency and tenant handling

Persistence fields

Subject to an approved data-class policy, model the following logical fields: local tenant ID, ERP vehicle ID, API Client ID or secret reference, VIN input and confirmed VIN, tapiId, returned provider, incomplete, matchLevel, category completeness, approved parts result fields including required per-position tapiGenArt and vdi, cart request positions, cart complete and results, originating operationId, OpenAPI version 1.71.0, request hash, idempotency key where applicable, HTTP status, job fields, response timestamps, billing headers and structured error status/code. This list defines integration state, not permission to retain a complete response. Keep only the minimum permitted subset for the approved duration, and never persist the raw API key in business tables.

Acceptance tests

  1. A documented sandbox VIN completes the direct branch and stores distinct VIN and tapiId fields.
  2. The redirect branch rejects a callback with wrong or reused state, accepts status=completed, and handles status=cancelled without creating a vehicle mapping.
  3. A 200 parts result preserves provider, matchLevel, category completeness, quantities, every tapiGenArt and every required vdi array, including [].
  4. A sandbox 202 parts job survives a process restart and reaches succeeded without resubmitting the initiating call.
  5. A unit test built from the documented production failed job example treats nested error.code as terminal even though polling returned HTTP 200.
  6. A completed redirect that began with a partial VIN can use its tapiId routes but cannot enter parts or cart calls because the callback supplies no completed VIN.
  7. A schema-valid but incomplete VIN-parts payload enters quarantine instead of creating an ERP parts list.
  8. Duplicate OE cart positions remain distinct and in order.
  9. fits=false plus complete=false is rendered as unresolved, not incompatible.
  10. Repeating a cart check with the same idempotency key produces no duplicate local operation and accepts X-Tapinoma-Idempotent-Replay: true.
  11. Calls for two Clients can run concurrently, while two calls for one Client are serialised.

Operational boundary

The ERP must remain the recovery authority for policy-approved data and must not use API-side persistence as its recovery store. Retain data only under an approved policy.

Recipe 2: DMS document extraction

Goal and non-goals

Goal: extract a stable international vehicle-registration structure, rich source-backed vehicle/equipment data or the documented generic automotive document structure and attach the derivative result to a DMS record.

Non-goals: replacing the original document, proving authenticity, making OCR output legally authoritative, or using a generic document response as a vehicle-registration standard.

Prerequisites

System-of-record boundary

The DMS owns the original bytes, access control, legal hold, source checksum, document version and deletion policy. The tapinomahub response is a derived extraction. Human corrections belong in a separate DMS layer; never overwrite the original document, and retain an unmodified response only when the approved policy explicitly permits it.

Operation sequence

  1. For an international vehicle registration document, call POST /scanner/document/registration/international (extractInternationalRegistrationDocument) with {fileUrl, quality?} and an Idempotency-Key. New integrations should use fileUrl, not request-property or path aliases. Use POST /scanner/document/registration (extractRegistrationDocument) only when the national {status,data} response contract is explicitly required.
  2. Require HTTP 200 and format="tapinoma.vehicle-registration.v2". Persist the document, registration, holder, inspection, vehicle, fields and warnings blocks as one versioned result.
  3. Index fields[] using the canonical code. Keep sourceCode, canonical English name, normalized value and sourceValue together. Do not replace proper names, addresses or identifiers with translated guesses.
  4. For a repair cost calculation, appraisal, invoice or similar calculation document, call POST /scanner/document/calculation (extractCalculationDocument) with {fileUrl, quality?} and a separate Idempotency-Key. Its fixed DocumentCalculationResponse has 12 required top-level fields: documentType, documentSubType, source, document, vehicle, financials, repairAssessment, parts, labor, paint, notes and equipment. There is no request field for a caller-defined output schema. Preserve equipment[].codes[].value as a string together with equipmentKind, sourceSection and sourcePage.
  5. For a vehicle order or configuration where detailed identifiers, powertrain, equipment, warnings or the explicit sensitive-data boundary are needed, call POST /scanner/document/vehicle (extractVehicleDocument) with {fileUrl, quality?, includeSensitiveData?}. Keep includeSensitiveData=false unless the caller has an approved, purpose-bound need for the omitted personal, bank, payment or contract fields. Validate schemaVersion="1.0" and persist only the approved subset of document, vehicle, equipment, sensitiveData and warnings.
  6. The four old paths are deprecated compatibility aliases and never HTTP redirects: /scanner/document/extract, /scanner/vehicle-document/extract, /scanner/registration-document and /scanner/registration-document/v2. Follow the documented response and idempotency behaviour for each path.
  7. If the input is a photograph whose sole purpose is VIN reading, call POST /scanner/vin/extract (extractVinFromImage) with {imageUrl, quality?}. Continue only when the returned vin is non-null and has all 17 characters.
  8. If DMS workflow explicitly requires vehicle enrichment, pass the confirmed VIN into the ERP recipe's documented VIN entry branch. Keep the extraction result and vehicle lookup result as separate records.

Identifiers carried forward

Identifier Source Destination and rule
DMS document ID and version DMS Local correlation and idempotency-key derivation only.
Source checksum DMS Detect source changes; a changed document is a new business operation and needs a new idempotency key.
fields[].code Registration response Canonical field mapping key; retain sourceCode and sourceValue for traceability.
Calculation equipment[].codes[] Calculation response Keep code system, original string value, nullable normalizedValue, equipmentKind, sourceSection and sourcePage together; never coerce leading-zero codes to numbers.
equipment[].codes[] Rich vehicle-document response Keep code system, original string value and nullable normalizedValue together; never coerce leading-zero codes to numbers.
vin vehicle block or VIN-image response Optional input to a separate VIN workflow only after format and completeness validation.

Validation, errors, idempotency and tenant handling

Persistence fields

Subject to an approved data-class policy, model the following logical fields: tenant ID, DMS document ID and version, source checksum, a policy-permitted source reference, operation ID, OpenAPI version, quality, sensitive-data opt-in state, request hash, idempotency key where permitted, response format or schemaVersion where present, only the response fields approved by the mapping, source-preserving vehicle/equipment code strings, warnings, extraction timestamp, validation status, human-review status, HTTP status, billing headers and structured error. This list is not permission to retain the raw response or source document. Minimise and field-restrict holder and other personal data; block production persistence until purpose, duration, deletion and access rules are approved.

Acceptance tests

  1. A sandbox international-registration call validates against its named OpenAPI response and maps every fields[] element without losing sourceCode or sourceValue.
  2. A missing optional value remains null; the adapter does not populate it from another field.
  3. Calculation extraction maps empty parts, labor, paint and notes arrays without converting them to null and always returns the twelfth top-level equipment field.
  4. A multi-page calculation example preserves every documented equipment entry, keeps codes[].value as a string including leading zeroes, and does not mix entries from another section into equipment.
  5. Rich vehicle-document extraction keeps leading-zero equipment codes as strings and never infers availability from equipmentKind.
  6. Vehicle-document extraction with includeSensitiveData=true sends no idempotency key and persists no replayable raw response.
  7. Replaying one key and body through either path of the national-registration, international-registration or rich-vehicle alias pair produces the same result without a second execution or charge.
  8. Reusing a legacy document-extraction key on canonical calculation extraction, or the reverse, returns HTTP 409; the adapter neither accepts an 11/12-field cross-contract replay nor invents a replacement key.
  9. A VIN-image result with vin=null creates a review outcome and does not start a vehicle lookup.
  10. A source-version change creates a new idempotency key; a network retry of the same version reuses the previous key where the operation permits it.
  11. The same document ID in two tenants produces isolated calls with two different Client keys.
  12. Logs and support payloads exclude the API key and unnecessary document content.

Operational boundary

Apply the DMS's approved retention and deletion policy to source files, extracted holder data and scanner results. Do not infer a retention period from the API response.

Recipe 3: marketplace or shop catalogue enrichment

Goal and non-goals

Goal: turn a confirmed OE number into traceable part master data, indicative pricing, a publication-ready marketplace article — eBay listing title, category, item specifics, keywords and shop SEO text — and optional synthetic publication images; optionally generate text for a vehicle already identified by tapiId.

Non-goals: automatically publishing unchecked content, setting the seller's final price, proving vehicle fitment from references, or presenting a generated image as a photograph of the stock item.

Prerequisites

System-of-record boundary

The shop owns SKU identity, stock, tax, final price, offer state, publication approval, customer orders and its stored media. The tapinomahub API supplies normalized references, documented type-level fitment, descriptive enrichment, an indicative price evaluation and generated assets. fitment, references, referenceNumbers, replacementChain, aftermarket references, VDI codes and generated visuals remain evidence or enrichment, not a stock identity or installation guarantee.

Operation sequence

  1. Call GET /parts/oe/normalize?oeNumber={raw}&manufacturer={optional} (normalizeOeNumber). Continue automatically only when status=matched and normalizedOeNumber is non-null. Persist version, matchRule, confidence, replacement-family metadata and candidates even when the outcome is not matched.
  2. Call GET /parts/oe/{oeNumber} (getOePart) with the confirmed normalizedOeNumber. Carry forward response normalizedOeNumber, tapiGenArt, vdi, part, fitment, replacementChain, references and referenceNumbers. Preserve part.name exactly as returned, including null. Treat tapiGenArt and vdi as independent best-effort enrichments: accept null and [] without discarding the confirmed base part or the other classification. Keep vehicleTypeKey opaque and preserve every fitment entry's criteria with it. fitment contains the available type-level assignments and can be empty. Preserve each {manufacturer, numbers} reference group and the exact closed referenceNumbers.oe_oem_reference_numbers[] list. Read relationship direction only from replacementChain; do not derive either reference view from that chain or the chain from those views.
  3. For an indicative market range, call GET /parts/oe/{oeNumber}/price (getOePrice) with explicit condition, ISO 4217 currency and vehicleType. Persist the returned parameters, result, new, used and priceRecommendation together.
  4. For the marketplace article itself, call GET /parts/oe/{oeNumber}/seo (getPartSeo) with the configured marketplaceId, language and vehicleType, and map the response onto the listing form: content.ebayTitle onto the eBay listing headline within its 80-character limit, categoryId onto the marketplace category of the requested marketplaceId, itemSpecifics[] onto the article's item-specific fields as {name, value[]}, keywords[] onto search terms, product onto product naming, and content.title, content.h1, content.metaTitle, content.metaDescription, content.slug and content.bulletPoints onto the shop page. Persist the returned publication fields with the generated text. 404 seo_no_exact_match is a documented no-match for that OE number and marketplace, not a failure of the item flow.
  5. If a standalone part designation needs translation, call GET /translation/translations?sourceLanguage={language}&text={one-part-designation} (translatePartName). The text parameter must contain exactly one part designation, not a title, sentence, list or description.
  6. For synthetic publication images, call POST /vision/part/generate (generatePartImagesFromOeNumber) with an Idempotency-Key and VisionPartGenerationRequest:
    • oeNumber is required.
    • angles contains one to six values from front, rear, left, right, left_45 and right_45 when supplied.
    • surfaceFinish=manufacturer_color requires colorCode; supplying colorCode with auto selects that finish.
    • referenceImageUrls contains at most eight authorised URLs.
    • sourceMode is one of references_required, references_preferred or knowledge_only; knowledge_only must not be combined with reference URLs.
    • generateBaseModel defaults to true. Set it explicitly to false unless the shop needs the optional model asset and has a storage policy for it.
  7. The generation call returns 202. Retain the job reference and poll GET /vision/part/generation-jobs/{jobId} (getPartImageGenerationJob) until terminal. On success, validate every required VisionPartGenerationResult block: format, oeNumber, coverage, basis, synthetic, disclosure, finish, part, dimensions, materials, paintableSurface, baseModel, views, identifierProtection, limitations and generatedAt. Copy every required views[].imageUrl into the customer workflow and verify views[].sha256. When baseModel.status=generated and baseModel.modelUrl is non-null, process that asset too and verify baseModel.sha256 when the field is present.
  8. For a vehicle listing, only when the Client already owns a valid tapiId, call POST /vehicles/{tapiId}/listing (composeVehicleListing) with {quality?, language?, notes?} and an Idempotency-Key. language is de, en or fr. Persist format, tapiId, language, title, description, highlights, documentedEquipmentCount and truncated.

Identifiers carried forward

Identifier Source Destination and rule
Shop item ID / SKU Shop Local correlation only.
Raw OE input Shop Normalization audit; never overwrite it with the normalized result.
normalizedOeNumber Normalization and base-part responses All later OE calls after a confirmed match.
tapiGenArt and vdi[] Base-part response Classification fields only; not a substitute for the OE number.
vehicleTypeKey Confirmed OE response fitment[] Opaque type-level assignment key; it is neither VIN nor tapiId and has no public resolver in API 1.71.0.
references[].manufacturer and references[].numbers[] Base-part response Confirmed reference and comparison numbers in their returned manufacturer groups; retain no inferred direction, interchangeability or fitment claim.
referenceNumbers.oe_oem_reference_numbers[] Base-part response Consolidated confirmed OE/OEM references including the confirmed OE number; do not derive this list from replacementChain.
jobId / statusUrl Image-generation 202 getPartImageGenerationJob only.
sha256 Generated view Verify the copied asset and deduplicate local media safely.
tapiId Earlier Client-owned VIN flow Optional vehicle-listing operation; never derive it from shop content.

Validation, errors, idempotency and tenant handling

Persistence fields

Subject to an approved data-class policy, model the following logical fields: tenant ID, shop item ID/SKU, raw and normalized OE values, manufacturer hint, normalization rule-set version/status/rule/confidence, tapiGenArt, VDI codes, policy-permitted base-part, fitment, reference-family and directed replacement facts, price request dimensions and response ranges, SEO request dimensions and returned publication/content fields, translation source text/language and result, image-generation request, job state, required generation provenance, copied asset locator and checksum, tapiId for vehicle listings, idempotency keys, operation IDs, OpenAPI version, timestamps, billing headers, publication-review decision and structured errors. Retain only the minimum approved subset; no complete payload or generated asset has an implied storage or redistribution right.

Acceptance tests

  1. matched normalization proceeds with the returned normalizedOeNumber; all other statuses stop before the base-part call.
  2. The adapter accepts both populated and empty fitment lists, keeps every criteria[] value with its vehicleTypeKey and never manufactures assignments locally.
  3. A reference or comparison number, replacement edge, aftermarket reference or VDI code never creates a compatibility claim by itself; returned fitment[] remains type-level evidence rather than a VIN-specific guarantee.
  4. The price adapter preserves condition, currency, vehicleType, result and confidence with the range.
  5. The SEO adapter accepts the documented publication fields, fills the listing title, category and item specifics from content.ebayTitle, categoryId and itemSpecifics[], tolerates a null categoryId and an empty itemSpecifics[], and handles optional live-availability metadata separately.
  6. Image generation persists 202 before polling, resumes after restart and verifies each downloaded asset against sha256.
  7. A generated base-model asset is copied to controlled storage and its checksum is verified when present; generateBaseModel=false creates no dependency on modelUrl.
  8. knowledge_only plus referenceImageUrls is rejected before transmission.
  9. The published image is visibly governed by the returned synthetic disclosure and never labelled as a stock photograph.
  10. Replaying the generation request with its original idempotency key does not create a second local job or media set.

Operational boundary

When the approved data policy permits copying an accepted generated asset, move it to controlled storage before publication and verify its checksum. Do not treat a returned asset URL as permanent storage.

Recipe 4: parts inventory and dismantling

Goal and non-goals

Goal: identify photographed parts, normalize candidate OE numbers, attach public part classifications, evaluate inventory and recall context, and optionally rank expected dismantling value for a VIN.

Non-goals: certifying a physical part's identity from OCR alone, proving that a reference fits a particular vehicle, replacing a dismantler's inspection, or turning an economic evaluation into an automatic purchase decision.

Prerequisites

System-of-record boundary

The inventory or dismantling system owns the physical unit, provenance, serial/instance identifiers, measured condition, location, quantity, cost, sale state and disposal decision. The tapinomahub API provides OCR candidates, OE reference data, classification codes, indicative price data, series-level recall context and a modelled economic evaluation.

Operation sequence

Part-unit intake

  1. Call POST /scanner/label/extract-partnumbers (extractLabelPartNumbers) with {imageUrl, quality?} and an Idempotency-Key.
  2. Read partNumbers[]; production and sandbox use the same response shape. Preserve every candidate and do not select one only because it appears first.
  3. For each candidate, call GET /parts/oe/normalize (normalizeOeNumber) with oeNumber and an optional manufacturer hint. Proceed only with a confirmed matched result.
  4. Call GET /parts/oe/{oeNumber} (getOePart) for the confirmed normalized number. Store tapiGenArt, vdi[], part, fitment, replacementChain, references and referenceNumbers as distinct reference data linked to the physical stock unit. Preserve part.name as the returned string or null. Treat tapiGenArt: null and vdi: [] as independent missing enrichments, not as a failed base lookup. Keep criteria[] attached to its opaque vehicleTypeKey. fitment contains the available type-level assignments and can be empty. Preserve grouped and consolidated references as returned, and never derive them from the replacement chain or turn them into an undirected substitution rule.
  5. Load the classification catalogue with GET /vdi (getVdiCatalog). Prefer the unpaginated response when feasible. If paging is required, use limit from 1 to 500, advance offset by page.returned, and stop on page.hasMore=false. Version the local index by stand.catalogVersion. This catalogue does not return part mappings; use only VDI codes already returned by getOePart or by a successful VIN-parts position for the link.
  6. Optionally call GET /parts/oe/{oeNumber}/aftermarket-references (getOeAftermarketReferences). These are reference records, not interchange or fitment guarantees. Prefer an unpaginated response; if paging, use page.returned and page.hasMore.
  7. Optionally call GET /parts/oe/{oeNumber}/price (getOePrice) with explicit condition, currency and vehicleType.
  8. For cluster-level market evidence, call GET /parts/intelligence (resolvePartIntelligence) with the confirmed OE number, explicit market dimensions and its required Idempotency-Key. On 202, persist and poll only GET /parts/intelligence/jobs/{jobId} (getPartIntelligenceJob). On completion, keep the returned clusterId, clusterVersion, scope and snapshot metadata together.
  9. With a known cluster, read current Intelligence through GET /parts/clusters/{clusterId}/intelligence (getPartClusterIntelligence) or materialized snapshots through GET /parts/clusters/{clusterId}/history (getPartClusterIntelligenceHistory). Both require their own idempotency key; history also requires from and to and never starts a job.
  10. For recall context, batch one to 100 stock positions into POST /recalls/parts (matchPartRecalls). Send each as {oeNumber, reference, vehicle?} where reference is the local stock-unit key, and use an Idempotency-Key. Map results by returned reference and preserve register data versions and the scope notice.

Vehicle-level dismantling evaluation

  1. Call GET /vin/{vin}/economic-evaluation (getVinEconomicEvaluation) with a known full VIN and the documented business parameters such as country, condition, vehicleType, maxPricedParts, recoveryRate and costPerPart.
  2. On 200, persist the result. On 202, persist the job and poll GET /vin/economic-evaluation/jobs/{jobId} (getVinEconomicEvaluationJob).
  3. Consume the echoed assumptions, coverage, revenuePotential, purchaseRecommendation and ranked parts together. Preserve warnings and liveAvailability when present; both are optional. Incomplete coverage makes the totals a lower bound.
  4. If the workflow needs the underlying vehicle-related parts without an economic model, call GET /vin/{vin}/parts (getPartsByVin) and follow its 200/202 branch via GET /vin/parts/jobs/{jobId} (getVinPartsJob). Retain matchLevel and missingCategories.
  5. When cluster-based valuation is required, call POST /vin/economic-intelligence (createVinEconomicIntelligence) with VIN, market country, currency, condition and a separate Idempotency-Key. On 202, poll only GET /vin/economic-intelligence/jobs/{jobId} (getVinEconomicIntelligenceJob). Keep final evaluationId, pipeline, portfolio, candidates, confidence, provenance and warnings together; do not substitute this response for the legacy evaluation shape.

Identifiers carried forward

Identifier Source Destination and rule
Stock-unit reference Inventory matchPartRecalls.positions[].reference; returned unchanged and used for line mapping.
OCR candidate Label extraction Normalization input only; not yet a confirmed stock identity.
normalizedOeNumber Normalization Base part, references, price and recall request after status=matched.
tapiGenArt Base-part or VIN-parts response Stable tapinomahub classification linked to the stock unit; not an OE number.
vdi[] Base-part or VIN-parts response Required confirmed mapping array, including []; join non-empty values to the separately versioned VDI catalogue.
VIN and tapiId Vehicle flow Keep both; VIN identifies the individual input and tapiId the technical type.
Economic jobId 202 response getVinEconomicEvaluationJob only.
clusterId and clusterVersion Part or VIN Intelligence result Current/history reads and snapshot correlation; never substitute an OE number.
Part Intelligence jobId / statusUrl Resolver or current-snapshot 202 getPartIntelligenceJob only.
VIN Intelligence jobId / statusUrl VIN Intelligence 202 getVinEconomicIntelligenceJob only.
evaluationId Final VIN Intelligence response Audit correlation only; no public lookup operation consumes it.

Validation, errors, idempotency and tenant handling

Persistence fields

Subject to an approved data-class policy, model the following logical fields: tenant ID, stock-unit and vehicle-unit IDs, a policy-permitted source image reference/checksum, OCR response variant and candidates, normalization input and approved result fields, confirmed normalized OE number, tapiGenArt, VDI codes and catalogue version, permitted part/replacement/reference facts, price dimensions and result, recall position reference/status/measures/register versions/notice, VIN, tapiId, parts matchLevel and completeness, cluster ID/version, market scope, asOf, snapshot ID, separated evidence blocks, prices, standtime, trend, scores and confidence, VIN Intelligence evaluationId, pipeline/portfolio/candidates/provenance/warnings, job family/state, idempotency keys, operation IDs, OpenAPI version, timestamps, billing headers, review decisions and structured errors. Retain only the minimum approved subset for its stated purpose and duration; this model is not permission to retain images or full responses.

Acceptance tests

  1. The adapter accepts both declared label-response property names and preserves each candidate as a separate value.
  2. Two OCR candidates remain separate until each normalization result is evaluated.
  3. A VDI catalogue refresh is keyed by catalogVersion; an OE mapping is never inferred from catalogue content.
  4. Unpaginated and paginated classification loads produce the same ordered entry set; pagination stops by page.hasMore.
  5. Recall batch responses map back to duplicate OE numbers by unique local reference, not by number alone.
  6. A 202 economic job resumes after restart, and a nested failed status never creates a recommendation.
  7. A response with incomplete coverage is labelled as a lower bound and cannot pass an automatic purchase threshold without the configured review rule.
  8. Two tenants using the same stock-unit string remain isolated by Client key.
  9. A resolver 202 survives restart and polls only the part-Intelligence status route; its jobId fails safely on the VIN status route.
  10. History never creates a job or a synthetic missing point, and cluster version, snapshot ID, scope and asOf remain attached to every stored point.
  11. Observed-supply counts never populate verified-sale counts, and publishable=false cannot trigger automated pricing or acquisition.
  12. Final VIN polling yields an evaluationId equal to the preceding jobId and requestId in the current pilot; the adapter retains the three field roles and never generalises this intentional alias to another job family.

Operational boundary

Retain submitted images, OCR results, jobs and evaluation evidence only as permitted by the approved policy. Do not rely on API-side history as the integrating system's recovery store.

Recipe 5: vehicle intake

Goal and non-goals

Goal: create a vehicle file from a registration-document URL, optionally add a visual condition report, preserve partial component outcomes and continue with technical data, recalls or listing text.

Non-goals: transferring ownership of the original document, performing a roadworthiness test, creating an expert appraisal, or treating tapiId as the physical vehicle's unique identifier.

Prerequisites

System-of-record boundary

The intake system owns the physical vehicle, source documents and photos, consent/legal basis, custody, workflow status, human inspection, keeper-data policy and sale decision. The tapinomahub API returns a document extraction, matched vehicle-type data and an optional visual-only condition report.

Operation sequence

  1. Call POST /vehicles/intake (intakeVehicle) with {fileUrl, photoUrls?, country?, includeOwner?} and an Idempotency-Key.
  2. A successful intake is synchronous HTTP 200; this operation has no 202 branch. Validate format, registrationDocument, vin, provider, tapiId, vehicle, conditionReport, components and complete.
  3. Evaluate each component independently:
    • components.registrationDocument is delivered on every successful response.
    • components.vehicle is delivered, vin_not_readable or unavailable.
    • components.conditionReport is delivered, failed or not_requested.
    • complete=true only when every requested component was delivered.
  4. If tapiId is non-null, optionally call GET /vehicles/{tapiId} (getVehicleByTapiId) to retrieve the technical vehicle-type record.
  5. If a new or larger photo set must be assessed independently, call POST /vision/condition-report (reportVehicleCondition) with one to eight imageUrls, optional quality and a new Idempotency-Key. Preserve gradable, grade, reason, all eight zones, limitations, imageCount and visualOnly.
  6. If VIN is non-null, optionally call GET /recalls/vehicles/{vin} (matchVehicleRecalls) and preserve measures, register versions and the scope notice.
  7. If tapiId is non-null and publication text is required, call POST /vehicles/{tapiId}/listing (composeVehicleListing) with {quality?, language?, notes?} and a separate Idempotency-Key; language is de, en or fr.

Identifiers carried forward

Identifier Source Destination and rule
Local intake ID Intake system Local correlation only and parent of every derivative record.
vin Intake response Recall or later VIN workflows only when non-null; retain separately from tapiId.
tapiId Intake response Technical type and listing operations only when non-null.
provider Intake response Preserve the exact numeric value returned by the API.
Component status Intake response Drives partial-success workflow and refund reconciliation; never replace with HTTP status alone.

Validation, errors, idempotency and tenant handling

Persistence fields

Subject to an approved data-class policy, model the following logical fields: tenant ID, local intake ID, policy-permitted document/photo references and checksums, includeOwner, country, request hash, intake idempotency key, response format, approved registration-document fields, VIN, tapiId, numeric provider, approved vehicle and condition fields, each component status, complete, permitted later technical/recall/listing facts, separate idempotency keys, operation IDs, OpenAPI version, timestamps, billing headers, human-review status and structured errors. Retain only the minimum approved subset and block production persistence until document, photo and holder-data rules are approved.

Acceptance tests

  1. A complete sandbox intake validates all required response fields and links the local intake ID, VIN and tapiId without conflating them.
  2. A fixture with components.vehicle=vin_not_readable persists the document result, leaves vehicle fields nullable and does not call VIN-dependent endpoints.
  3. A fixture with components.conditionReport=failed records a partial success and does not discard delivered document or vehicle data.
  4. includeOwner=false never produces a locally populated owner record.
  5. A condition report with gradable=false and grade=null enters review and preserves all limitations.
  6. Replaying the intake key produces one local intake result and recognises the idempotent response headers.
  7. A tapiId obtained under one Client is not resolved using another tenant's key.

Operational boundary

The intake system must remain the recovery and retention authority for the vehicle file, source document, photos, holder data and condition report. Apply an approved retention policy to every stored field.

Recipe 6: multi-tenant middleware

Goal and non-goals

Goal: provide a controlled server-side adapter that provisions isolated workspaces, routes calls with the correct key, serialises per-Client work, normalises retries and polling, and exposes usage to operations.

Non-goals: exposing the master key, accepting arbitrary pass-through paths, inventing endpoint keys, combining tenant data, or becoming the system of record for ERP, DMS, marketplace, inventory or vehicle workflows.

Prerequisites

System-of-record boundary

The middleware owns Tenant-to-Client mapping, secret references, request correlation, idempotency state, queue state, job state and transport audit. The calling business system owns its source and result records. tapinomahub owns API authentication, Client-level billing/limits and the returned service result. The middleware must not merge results merely because two Tenants used the same VIN, OE number, jobId or local reference.

Operation sequence

Tenant provisioning

  1. Reserve the local externalReference in a pending provisioning record before any network call.
  2. With the master key, call POST /client/partner-workspaces (createPartnerWorkspace) with no Idempotency-Key. Send:
    • required name, externalReference and one to 50 contract-approved endpointKeys;
    • optional sponsorship and applicationLabel.
    • Omit rateLimits in this call: OpenAPI exposes that array but does not define its item fields. Apply limits through the typed operation in step 4.
  3. On 201, validate that workspace.id and workspace.externalReference are present before activating the tenant; the top-level workspace is required but its inner properties are not marked required by the current schema. Then atomically persist those values, apiKey.id, a secret-manager reference to the one-time raw apiKey.key, allowlist and sponsorshipGrant. The apiKey object's id, label, key and createdAt are required. Never write the raw key to logs or ordinary database columns.
  4. If limits were not final at provisioning, call PUT /client/users/{clientId}/rate-limits (replaceClientUserRateLimits) only after contractual clarification or controlled onboarding configuration has established the Client ID independently of workspace.id. Use the Master client key, {rateLimits} and an Idempotency-Key. Each RateLimitInput supplies endpointKey, windowSeconds, maxRequests, optional apiKeyId and optional isActive.
  5. If sponsorship terms require a later explicit grant, call PUT /client/sponsorship-grants/{grantReference} (upsertSponsorshipGrant) with the Master client key, a stable grantReference, an explicitly sourced beneficiaryClientId, approved endpointKeys and optional contract fields. Use an Idempotency-Key; do not infer beneficiaryClientId from workspace.id.
  6. If the beneficiary is authorised to choose sponsor terms, use its own tenant key for PUT /client/sponsorship-grants/received/{grantReference}/billing-mode (chooseSponsorshipBillingMode) with {mode, sponsorClientId?} and an Idempotency-Key.

Runtime routing

  1. Authenticate the middleware caller and resolve exactly one local tenant record.
  2. Reject any requested capability outside that Tenant's local allowlist before selecting a tapinomahub path.
  3. Load only that Tenant's API-key secret and enqueue the call by the stable local Tenant record that represents exactly one tapinomahub Client, or by the explicitly established Client ID. Never partition concurrency by API-key ID: several keys can belong to one Client and must share its single queue. Then construct a request for an explicitly supported operation.
  4. Obtain or create a durable business-operation idempotency key only when the selected operation declares it. This includes the three Intelligence data GETs; omit it from both Intelligence status polls and other GETs that do not declare it.
  5. Apply the shared status/error policy. Persist a 202 before releasing the Client queue; schedule the documented poll through the same per-Client queue and matching tenant key.
  6. Expose operational consumption using either GET /client/usage (getClientUsage) with the Tenant key or, only when the {clientId} mapping is explicitly established, GET /client/users/{clientId}/usage (getClientUserUsage) with the Master client key. GET /client/credits (getClientCredits) reports the authenticated Client's current balance or partner billing mode.

Key and Client lifecycle

  1. Create an additional Tenant key with POST /client/users/{clientId}/keys (createClientUserApiKey) only for an explicitly established Client ID, using the Master client key and optional {label}. Do not send Idempotency-Key. Before cutover, require apiKey.id, apiKey.label, apiKey.key and apiKey.createdAt; neither the response wrapper nor ApiKeyCreateResponse currently marks these fields as required. If any is missing, enter reconciliation_required and do not repeat the call automatically. Store a complete raw key once as for initial provisioning.
  2. Move traffic to the new secret only after a successful authentication test such as GET /ping (getSystemStatus).
  3. Deactivate a Tenant with PATCH /client/users/{clientId} (updateClientUser) only for an explicitly established Client ID and {isActive:false} using an Idempotency-Key. Confirm subsequent Tenant-key calls are rejected before marking offboarding complete locally.

Identifiers carried forward

Identifier Source Destination and rule
Local tenant ID Middleware Primary partition key for every secret, request, result and job record.
externalReference Middleware One stable provisioning reference per end customer; duplicate submission returns workspace_reference_exists.
workspace.id candidate and returned sponsorshipGrant.beneficiaryClientId Provisioning response Keep both as distinct fields. Do not use workspace.id in a {clientId} path unless the public contract explicitly establishes that relationship.
apiKey.id Provisioning/key response Secret metadata and optional key-scoped rate limit; it is not the secret.
Raw apiKey.key One-time success response Secret manager only; never returned to logs or recoverable from later public operations.
grantReference Sponsor Sponsorship changes and beneficiary billing-mode selection.
Business idempotency key Middleware One Client, operation and canonical request; stored before dispatch.
jobId / statusUrl Business 202 Tenant-partitioned polling record for the exact documented job operation.

Validation, errors, idempotency and tenant handling

Persistence fields

Subject to an approved data-class policy, model the following logical fields: local tenant ID, provisioning state, externalReference, separately sourced Client/workspace identifiers, parent/master relation, active state, allowed operation IDs, contract-supplied endpoint keys, API-key ID/label/created time and secret reference, never the raw key, rate-limit facts, sponsorship reference and state, billing mode, queue lease/state, request correlation ID, canonical request hash, operation ID/path/method, idempotency key and replay flags, HTTP status, job fields, retry schedule, response schema version, billing/warning headers, permitted usage facts, structured error and audit timestamps. Persist only the minimum approved subset; key-secret handling follows the secret-store rule independently of business-data retention.

Acceptance tests

  1. Provisioning returns one Client and stores the one-time raw key only in the secret manager; logs and database exports contain no secret.
  2. A workspace success missing workspace.id or workspace.externalReference and an additional-key success missing raw key material both enter reconciliation rather than activation.
  3. Provisioning omits untyped rateLimits; the subsequent typed replacement call persists the returned limit set.
  4. An uncertain provisioning response enters reconciliation and is not automatically repeated.
  5. A caller cannot override its resolved Client key or invoke an operation outside the local allowlist.
  6. Two tenants run in parallel, while two requests for one Client execute serially.
  7. A 202 job remains tenant-partitioned, survives restart and polls with the same Client key.
  8. Every operation declaring idempotency has a durable pre-dispatch record; Intelligence data GETs carry the header and GET status polls do not.
  9. 409 idempotency_key_conflict, 409 idempotency_request_in_progress and 409 idempotency_result_unavailable reach distinct middleware states.
  10. Once a Client ID is explicitly established, usage obtained with the Master client key for {clientId} is assigned to the same Tenant as usage obtained with that Tenant's own key; without that mapping, the master-side assertion remains blocked.
  11. New-key cutover succeeds only after a tenant-key health call; deactivation makes the old tenant key fail authentication.

Operational boundaries

Use only documented lifecycle operations and fields. Supply endpoint-key mappings and recovery procedures as controlled configuration, quarantine incomplete provisioning responses, and apply the calling system's authorised retention policy to business data.