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 with GET /vin/{vin}/parts (getPartsByVin). Omit the optional provider query parameter unless the integration explicitly needs it; an explicit value that differs from the selection value associated with the VIN returns 409 vin_provider_mismatch. The response provider states the numeric selection used for the result, and a 202 job is documented only for provider=1.
    • 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, which the contract documents only for mode=vehicle, persist the returned cart-check jobId, statusUrl and createdAt, then poll only GET /vin/cart-check/jobs/{jobId} (getVinCartCheckJob).
  5. Optionally read the Client's current balance, plan consumption and endpoint-specific 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 only the policy-approved subset of 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 calculation or appraisal, or whenever calculation totals, a repair assessment or spare-parts, labour or paint line items are required, 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 content it withholds: without opt-in, sensitiveData.persons and sensitiveData.bankAccounts are empty, the scalar values of sensitiveData.contract are null, its amount objects contain only null, its fields list is empty, and document.title, document.number and document.date are null. Validate schemaVersion="1.0" and persist only the approved subset of document, vehicle, equipment, sensitiveData and warnings.
  6. For a vehicle purchase contract, call POST /scanner/document/purchase-contract (extractPurchaseContract) with {fileUrl, quality?}. Use an authorised HTTPS URL without credentials for PDF, JPEG, PNG or WebP. PDFs may contain at most 14,000,000 bytes and images at most 7,000,000 bytes. Do not send Idempotency-Key: this operation rejects it with HTTP 400 idempotency_key_not_supported. Validate schemaVersion="1.0" and the closed document, seller, buyer, vehicle, purchase, handover, condition, signatures and warnings fields. Keep only the approved personal-data subset. All responses carry Cache-Control: no-store. Signature presence and contractual declarations describe the source; they do not verify authenticity or legal validity.
  7. 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.
  8. 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.
  9. 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, marketplace article content that must be independently checked before publication — 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. The closed 200 always carries version, input, manufacturer, status, valid, reasons, lookupKey, normalizedOeNumber, matchRule, confidence, equivalentOeNumbers, equivalentOeNumberCount, equivalentOeNumbersTruncated and knownCandidates; retain the permitted subset of these fields 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 answer, not a failure of the item flow. Its closed body carries the requested oeNumber in normalized form together with marketplaceId, language and vehicleType; record the no-match against that combination.
  5. If a standalone part designation needs translation, call GET /translation/translations?sourceLanguage={designation-language}&text={one-part-designation} (translatePartName). Both parameters are required: sourceLanguage is the language of the submitted designation, not a target language (the schema allows at most 20 characters, while the contract's source_language_too_long example asks for a two-letter language code such as de), and text (at most 1000 characters) must contain exactly one individual part designation, not a title, heading, product or offer title, description, sentence, list or several designations. A 200 always contains the four strings en, fr, es and it; the operation has no target-language parameter.
  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 unique values from front, rear, left, right, left_45 and right_45; when it is omitted, all six are requested.
    • 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 (the default), 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) while status is queued or running. On failed, keep error.code and error.message, both required inside the optional error object. On succeeded, 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-group, consolidated reference-number 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 basis and disclosure fields (basis, synthetic, disclosure, coverage, limitations and each view's partIdentityVerified and verificationConfidence), 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. This is a pilot operation: the paid production pilot is commercially closed and delivers no real results without approved positive prices and atomic billing. Without a complete price configuration for the four initiating Intelligence operations, every Intelligence route, including both status polls, answers 503 market_intelligence_billing_unavailable regardless of the API key. On 202, persist and poll only GET /parts/intelligence/jobs/{jobId} (getPartIntelligenceJob). On completion, keep cluster.clusterId, cluster.clusterVersion, scope, asOf, validUntil and provenance.snapshotId 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 of at most 200 characters, returned unchanged, and the optional vehicle carries make, model, productionFrom and productionTo; use an Idempotency-Key. The response preserves position order. Map results by returned reference and keep each position's status (matched, no_measures or unresolved), unresolvedReason and measures[] with registers[].dataStand and the trilingual notice. 404 recall_positions_unresolvable means that no position could be assigned to a manufacturer.

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 jobId, statusUrl and createdAt and poll GET /vin/economic-evaluation/jobs/{jobId} (getVinEconomicEvaluationJob), observing Retry-After while status is queued or running. On succeeded, result contains the complete evaluation; on failed, the error object carries code vehicle_not_found (no-result outcome), vin_service_unavailable (technical failure), or market_pricing_unavailable (no usable market prices for the evaluation).
  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 the body fields vin, marketCountry, currency and condition and a separate Idempotency-Key. The route is deployed in the production pilot but remains disabled until the single-dispatch and billing reconciliation gate has been passed; while disabled it answers 503 with market_intelligence_billing_unavailable or market_intelligence_vin_not_enabled. 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 getVinEconomicEvaluation response 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 []. Catalogue code values match ^VDI4081_[0-9]+$, while mapping values match ^VDI4081_[0-9]+(?:_[0-9]+)?$, so a mapping value is not always identical to a catalogue code of the separately versioned 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 candidates from partNumbers[], 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 reads the single declared partNumbers[] property of the closed label response, accepts an empty list as “no part number recognised” 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 entry set; pagination advances offset by page.returned and stops on page.hasMore=false.
  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 of the same vehicle, optional quality (standard by default, enhanced or maximum) and a new Idempotency-Key. Preserve all nine required fields: format, gradable, grade (A, B, C or null), reason, summary, zones (each of front, flanke_links, flanke_rechts, heck, dach, innenraum, motorraum and sonstiges exactly once and in this order, each with its findings array), limitations, imageCount and visualOnly.
  6. If vin is non-null, optionally call GET /recalls/vehicles/{vin} (matchVehicleRecalls) without an Idempotency-Key and preserve vin, measures, measureCount, registers[] (register, dataStand, measureCount) and the notice object with its de, en and fr texts. A VIN that cannot be resolved returns 404 vin_not_resolvable.
  7. If tapiId is non-null and publication text is required, call POST /vehicles/{tapiId}/listing (composeVehicleListing) with the optional body {quality?, language?, notes?} and a separate Idempotency-Key; quality is standard (default), enhanced or maximum, language is de (default), en or fr, and notes is at most 500 characters, of which only factual statements are used. A foreign or unknown tapiId returns 404 vehicle_not_found.

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 the partial-success workflow and billing reconciliation under the agreed terms; 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 (administrative endpoints are not allowed);
    • optional sponsorship (default false; true means the caller grants the workspace sponsorship for exactly the enabled endpoints in the hub channel as part of the same request, otherwise the returned sponsorshipGrant is null) and applicationLabel (label for the generated API key).
    • Omit rateLimits in this call: its schema types the items only as object; the description refers to the same format as POST /client/users (createClientUser), which types them as RateLimitInput, and to a cap by operator limits. 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, one to 50 unique approved endpointKeys and, where the agreed terms require them, the optional channels (only hub), startsAt, exclusive endsAt, partnerTermsAllowed and beneficiaryMonthlyCapCents. Omitting partnerTermsAllowed or beneficiaryMonthlyCapCents leaves the existing setting unchanged; setting partnerTermsAllowed back to false drops a billing-mode choice already made. Use an Idempotency-Key; do not infer beneficiaryClientId from workspace.id.
  6. To set the beneficiary's billing terms, use its own tenant key for PUT /client/sponsorship-grants/received/{grantReference}/billing-mode (chooseSponsorshipBillingMode) with {mode, sponsorClientId?} and an Idempotency-Key. mode is own or partner; partner requires the sponsor's partnerTermsAllowed release, otherwise the operation returns 403. Send sponsorClientId when several sponsors use the same grantReference for this beneficiary; without it that case returns 409 ambiguous_grant_reference.

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 clientId, billingMode (own or partner), current balance (balanceCents, balance, currency), overdraft allowance (creditLimitCents) and available amount (availableCents); under partner, balanceCents, balance and currency are null and creditLimitCents and availableCents are null or absent.

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, unique within the Master client; a duplicate submission returns 409 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.