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
- The API has no tenant field. A Tenant is separated by assigning it its own Client, normally a Sub-user or partner-workspace response, and using that Client's API key.
- Never select a tenant by request-body data, VIN, OE number or a caller-supplied header. Resolve the authenticated tenant to one stored API-key secret before constructing the request.
- The contract allows one active API request per Client. Serialize calls for the same Client. Different Clients may run in parallel.
- A
tapiIdis accessible only with the Client key used for the documented VIN flow. A foreign or unknowntapiIdreturns404. - Never send an API key to browser code. The browser redirect flow returns a
redirectUrl; the server-side API key is not part of that redirect.
Error and retry rules
- Parse the HTTP status first and the stable top-level
errorvalue second. Do not branch on the human-readablemessage. - Treat
400,404and422as data or workflow decisions. Correct or reconcile the input before another attempt. - Treat
401and403as authentication, authorisation, Client-state or sandbox-availability decisions. Do not rotate or replace a key automatically. - Treat
402as a billing or coverage decision. Do not loop while the plan, balance or sponsorship state is unchanged. - For
409, branch on the documentederror. In particular, never hidevin_provider_mismatch,redirect_requiredor an idempotency conflict behind a generic retry. - For
429, honourRetry-After.client_request_in_progressalso means the Client queue is not correctly serialised. - Retry
500,502and503only when the corresponding error-catalogue entry is retryable, and use a bounded backoff. A write retry must reuse its originalIdempotency-Key. - The contract states that a pre-charged call ending at HTTP 400 or above is refunded automatically, subject to the explicitly documented combined-VIN exceptions. A refund does not turn a failed result into a success.
- Capture
X-Tapinoma-Billing-Source,X-Tapinoma-Billing-Bundle,X-Tapinoma-Sponsorship-GrantandX-Tapinoma-Usage-Warningwhen present. Warning text is for people and is not stable program input.
Idempotency rules
- Send
Idempotency-Keyonly to operations that declare it and follow that operation's schema. Intelligence explicitly requires it onGET /parts/intelligence,GET /parts/clusters/{clusterId}/intelligenceandGET /parts/clusters/{clusterId}/historyas well as its VIN POST; neither Intelligence status poll declares it. - Persist the key before the network call. The same Client, operation and canonical request body must reuse the same key after a timeout.
- A first stored success reports
X-Tapinoma-Idempotency-Stored: true; a replay reportsX-Tapinoma-Idempotent-Replay: true. Successful replay records are retained for 24 hours. - On
409 idempotency_request_in_progress, wait forRetry-Afterand repeat with the same key. On409 idempotency_key_conflict, stop because the key was used for different input. On409 idempotency_result_unavailable, do not substitute a new key automatically. - Treat the preceding stored/in-progress/result-unavailable rules as operation-specific. The four initiating Intelligence pilot operations document only the replay header and
409 idempotency_key_conflict; they do not declare the stored header, the other two central409states or a 24-hour retention promise. createClientUser,createClientUserApiKeyandcreatePartnerWorkspaceissue a raw API key and rejectIdempotency-Key. After an uncertain response, do not repeat them blindly.
Asynchronous rules
- Six documented job families have a polling contract: VIN parts, cart check, VIN economic evaluation, part-image generation, part-cluster refresh and VIN Economic Intelligence.
- A
202is an accepted job, not a business result. PersistjobId,statusUrl,createdAt, the originating operation and the original business key before acknowledging the request locally. - Honour both the
Retry-Afterheader and theretryAfterSecondsbody value. If both are present and differ, wait for the longer interval and record the discrepancy. Use the required fullstatusUrl; compareLocationand quarantine a mismatch rather than origin-resolving its current root-relative example. Do not resubmit the initiating operation merely because it returned202. - Continue for
queuedandrunning. Older status resources can express those states inside HTTP200; the two Intelligence status operations return HTTP202while pending and HTTP200with the final result. Require the workflow's usable result before completion and quarantine invalid state/result combinations. A requiredvdiarray that is present as[]is a complete VIN-parts field value, not a reason to continue polling. - Polling belongs to the original order and does not create a second order; rate limits and the single-active-request rule still apply.
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
- One server-side API key per ERP tenant or end customer.
- A legal basis for transmitting the VIN and using the returned data.
- A configured numeric
providervalue permitted by the Client's contract. - For browser-assisted route
1, an HTTPSreturnUrlwithout credentials or fragment and a server-side correlation store. The ERP should generate an opaquestateand enforce one-time use locally; the API contract only promises to return the value unchanged. Any continuation to VIN-dependent operations additionally requires a full 17-character VIN known independently of the callback. - An ERP business key for the vehicle and, for cart checks, a stable request key and an ordered list of at most 30 OE positions.
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
- Optionally verify connectivity with
GET /ping(getSystemStatus). Do not sendIdempotency-Keyto this read operation. - Establish vehicle context with exactly one of these entry branches:
- Direct server-to-server branch: call
GET /vin/{vin}/vehicle(getVehicleByVin) with explicitprovider=2orprovider=3, the two-lettercountry, and explicit choices for the optionalincludeEquipments,includeColorsandincludeTechnicalflags, or deliberately accept theirtruedefaults. Carry forward the returnedvin,tapiId,providerandincompletevalues. - Browser-assisted branch: server-side, call
POST /vin/redirect-sessions(createVinRedirectSession) with a 3–17-charactervininput, HTTPSreturnUrland an opaquestate. Use anIdempotency-Key. Redirect only the user agent to the returnedredirectUrlbeforeexpiresAt. On the callback, require the originalstateand, as a local anti-forgery control, reject a state already consumed by the ERP; accepttapiIdonly whenstatus=completed. A cancellation has notapiId. The callback does not contain the VIN completed in the browser. Fetch technical type data withGET /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.
- Direct server-to-server branch: call
- Only with a known full 17-character VIN, retrieve parts through route
1withGET /vin/{vin}/parts(getPartsByVin). Omit the optionalproviderfield unless the integration explicitly needs it; if it is sent, use the documented value consistently.- On
200, consumevin,tapiId,provider,matchLevel,allCategorySigns,missingCategoriesandparts; every part contains requiredtapiGenArtandvdifields, andvdi=[]is valid when no mapping is available. - On
202, persistjobId,statusUrl,vin,provider,createdAtand retry timing, then pollGET /vin/parts/jobs/{jobId}(getVinPartsJob).
- On
- If the ERP needs cart verification, call
POST /vin/cart-check(checkVinCart) with{vin, country?, mode, oeNumbers}and anIdempotency-Key.modeis exactlytypeorvehicle; preserve request order and duplicates.- On
200, mapresultsback to cart lines by array position and keepcompletewith every result. - On
202, persist the returned cart-checkjobIdandstatusUrl, then poll onlyGET /vin/cart-check/jobs/{jobId}(getVinCartCheckJob).
- On
- 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
- Reject an empty or unauthorised VIN before the API call. Do not infer missing VIN characters. Permit the documented 3–16-character input only for redirect-session creation; require an independently confirmed full 17-character VIN before parts, cart, economic or recall continuation.
- Before continuing, require the business identifiers used by the next step. In particular, quarantine a successful direct-vehicle payload that lacks
vin,tapiIdorprovider; the currentVinVehicleResponseschema does not mark these properties as required. - Likewise, validate a
VinPartsResponsebefore use. Quarantine a successful payload that lacksvin,provider,matchLevel,allCategorySigns,missingCategoriesorparts; the current schema describes these top-level properties but marks none of them as required.tapiIdis explicitly nullable. Every returned part must contain a validtapiGenArtand avdiarray; preserve an empty array instead of treating the position or job as incomplete. - A direct vehicle request with
provider=1can return409 redirect_required; start the documented redirect flow instead of retrying the same direct request. - On
409 vin_provider_mismatch, use the numeric route documented for the operation. Never silently switch the route. incomplete=truemeans the vehicle response is not an exact single-vehicle resolution.matchLevelmust remain attached to the parts data.- A non-empty
missingCategorieslist means the parts list is incomplete. In a cart response,fits=falsecombined withcomplete=falseis not a definitive exclusion. - Use idempotency for redirect-session creation and cart checks. The VIN and parts reads are GETs and must not receive the header.
- Queue all initiating calls and their polls per Client. Use the same Client key throughout the documented VIN flow.
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
- A documented sandbox VIN completes the direct branch and stores distinct VIN and
tapiIdfields. - The redirect branch rejects a callback with wrong or reused
state, acceptsstatus=completed, and handlesstatus=cancelledwithout creating a vehicle mapping. - A
200parts result preservesprovider,matchLevel, category completeness, quantities, everytapiGenArtand every requiredvdiarray, including[]. - A sandbox
202parts job survives a process restart and reachessucceededwithout resubmitting the initiating call. - A unit test built from the documented production
failedjob example treats nestederror.codeas terminal even though polling returned HTTP200. - A completed redirect that began with a partial VIN can use its
tapiIdroutes but cannot enter parts or cart calls because the callback supplies no completed VIN. - A schema-valid but incomplete VIN-parts payload enters quarantine instead of creating an ERP parts list.
- Duplicate OE cart positions remain distinct and in order.
fits=falsepluscomplete=falseis rendered as unresolved, not incompatible.- Repeating a cart check with the same idempotency key produces no duplicate local operation and accepts
X-Tapinoma-Idempotent-Replay: true. - 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
- A DMS document ID, immutable source version and checksum.
- A publicly retrievable
fileUrlthat the caller is authorised to transmit and process. - A per-tenant API key and a data-minimisation decision for holder data.
quality=standardfor personal-document operations; no other processing level is currently offered by the contract.- Parsers generated from the pinned OpenAPI schemas
RegistrationDocumentV2Response,DocumentCalculationResponse, the deprecated 11-fieldDocumentExtractionResponseand, when rich vehicle/equipment extraction is required,VehicleDocumentExtractionResponse.
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
- For an international vehicle registration document, call
POST /scanner/document/registration/international(extractInternationalRegistrationDocument) with{fileUrl, quality?}and anIdempotency-Key. New integrations should usefileUrl, not request-property or path aliases. UsePOST /scanner/document/registration(extractRegistrationDocument) only when the national{status,data}response contract is explicitly required. - Require HTTP
200andformat="tapinoma.vehicle-registration.v2". Persist thedocument,registration,holder,inspection,vehicle,fieldsandwarningsblocks as one versioned result. - Index
fields[]using the canonicalcode. KeepsourceCode, canonical Englishname, normalizedvalueandsourceValuetogether. Do not replace proper names, addresses or identifiers with translated guesses. - For a repair cost calculation, appraisal, invoice or similar calculation document, call
POST /scanner/document/calculation(extractCalculationDocument) with{fileUrl, quality?}and a separateIdempotency-Key. Its fixedDocumentCalculationResponsehas 12 required top-level fields:documentType,documentSubType,source,document,vehicle,financials,repairAssessment,parts,labor,paint,notesandequipment. There is no request field for a caller-defined output schema. Preserveequipment[].codes[].valueas a string together withequipmentKind,sourceSectionandsourcePage. - 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?}. KeepincludeSensitiveData=falseunless the caller has an approved, purpose-bound need for the omitted personal, bank, payment or contract fields. ValidateschemaVersion="1.0"and persist only the approved subset ofdocument,vehicle,equipment,sensitiveDataandwarnings. - The four old paths are deprecated compatibility aliases and never HTTP redirects:
/scanner/document/extract,/scanner/vehicle-document/extract,/scanner/registration-documentand/scanner/registration-document/v2. Follow the documented response and idempotency behaviour for each path. - 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 returnedvinis non-null and has all 17 characters. - 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
- Ensure the source URL is an authorised absolute URL and keep it available until the synchronous response completes. The contract does not promise that inaccessible private storage can be fetched.
- Validate the whole response against the named schema before indexing any field. Null and empty arrays are valid documented outcomes; do not manufacture missing values.
- Treat
documentTypeanddocumentSubTypefromextractCalculationDocumentor the deprecatedextractDocumentpath as uncontrolled strings, not enums. - In
extractVehicleDocument, validateequipmentKindandavailabilityindependently. Preservestandard,variantandspecialas the documented kind axis and do not derive availability or pricing from it. - A null VIN is a successful "not reliably readable" outcome, not a transport failure.
- Use a different idempotency key for the registration, rich vehicle-document, calculation and VIN-image business operations, even when they refer to the same source file. Reuse a key only for an identical retry. The canonical and deprecated paths for national registration, international registration and rich vehicle extraction share idempotency because their responses are identical. Never move one key between canonical calculation and deprecated document extraction: the latter omits
equipment, so cross-contract reuse fails with HTTP409. Do not send an idempotency key whenextractVehicleDocument.includeSensitiveData=true; the endpoint rejects it and never stores that sensitive response for replay. - Route the request with the source document's tenant key. Never select the key from data extracted from the document.
- Apply the shared error policy. In particular, do not retry an unfetchable URL indefinitely; first restore authorised reachability or issue a new URL.
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
- A sandbox international-registration call validates against its named OpenAPI response and maps every
fields[]element without losingsourceCodeorsourceValue. - A missing optional value remains null; the adapter does not populate it from another field.
- Calculation extraction maps empty
parts,labor,paintandnotesarrays without converting them to null and always returns the twelfth top-levelequipmentfield. - A multi-page calculation example preserves every documented equipment entry, keeps
codes[].valueas a string including leading zeroes, and does not mix entries from another section intoequipment. - Rich vehicle-document extraction keeps leading-zero equipment codes as strings and never infers
availabilityfromequipmentKind. - Vehicle-document extraction with
includeSensitiveData=truesends no idempotency key and persists no replayable raw response. - 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.
- 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. - A VIN-image result with
vin=nullcreates a review outcome and does not start a vehicle lookup. - 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.
- The same document ID in two tenants produces isolated calls with two different Client keys.
- 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
- A shop item ID and the original OE-number input.
- A configured manufacturer hint where available, target language,
vehicleType, currency and a contract-supportedmarketplaceIdenum value. The documented marketplaces are the eBay sitesEBAY_AT,EBAY_AU,EBAY_BE,EBAY_CA,EBAY_CH,EBAY_DE,EBAY_ES,EBAY_FR,EBAY_GB,EBAY_HK,EBAY_IE,EBAY_IT,EBAY_NL,EBAY_PL,EBAY_SGandEBAY_US, withEBAY_DEas the default. SEO languages arede,en,fr,es,it,nl,plandzh; do not invent enum values. - Publication rules for minimum content quality, live availability, synthetic disclosure and human review.
- Authorised reference-image URLs if image generation uses them.
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
- Call
GET /parts/oe/normalize?oeNumber={raw}&manufacturer={optional}(normalizeOeNumber). Continue automatically only whenstatus=matchedandnormalizedOeNumberis non-null. Persistversion,matchRule,confidence, replacement-family metadata and candidates even when the outcome is not matched. - Call
GET /parts/oe/{oeNumber}(getOePart) with the confirmednormalizedOeNumber. Carry forward responsenormalizedOeNumber,tapiGenArt,vdi,part,fitment,replacementChain,referencesandreferenceNumbers. Preservepart.nameexactly as returned, includingnull. TreattapiGenArtandvdias independent best-effort enrichments: acceptnulland[]without discarding the confirmed base part or the other classification. KeepvehicleTypeKeyopaque and preserve every fitment entry's criteria with it.fitmentcontains the available type-level assignments and can be empty. Preserve each{manufacturer, numbers}reference group and the exact closedreferenceNumbers.oe_oem_reference_numbers[]list. Read relationship direction only fromreplacementChain; do not derive either reference view from that chain or the chain from those views. - For an indicative market range, call
GET /parts/oe/{oeNumber}/price(getOePrice) with explicitcondition, ISO 4217currencyandvehicleType. Persist the returned parameters,result,new,usedandpriceRecommendationtogether. - For the marketplace article itself, call
GET /parts/oe/{oeNumber}/seo(getPartSeo) with the configuredmarketplaceId,languageandvehicleType, and map the response onto the listing form:content.ebayTitleonto the eBay listing headline within its 80-character limit,categoryIdonto the marketplace category of the requestedmarketplaceId,itemSpecifics[]onto the article's item-specific fields as{name, value[]},keywords[]onto search terms,productonto product naming, andcontent.title,content.h1,content.metaTitle,content.metaDescription,content.slugandcontent.bulletPointsonto the shop page. Persist the returned publication fields with the generated text.404 seo_no_exact_matchis a documented no-match for that OE number and marketplace, not a failure of the item flow. - If a standalone part designation needs translation, call
GET /translation/translations?sourceLanguage={language}&text={one-part-designation}(translatePartName). Thetextparameter must contain exactly one part designation, not a title, sentence, list or description. - For synthetic publication images, call
POST /vision/part/generate(generatePartImagesFromOeNumber) with anIdempotency-KeyandVisionPartGenerationRequest:oeNumberis required.anglescontains one to six values fromfront,rear,left,right,left_45andright_45when supplied.surfaceFinish=manufacturer_colorrequirescolorCode; supplyingcolorCodewithautoselects that finish.referenceImageUrlscontains at most eight authorised URLs.sourceModeis one ofreferences_required,references_preferredorknowledge_only;knowledge_onlymust not be combined with reference URLs.generateBaseModeldefaults totrue. Set it explicitly tofalseunless the shop needs the optional model asset and has a storage policy for it.
- The generation call returns
202. Retain the job reference and pollGET /vision/part/generation-jobs/{jobId}(getPartImageGenerationJob) until terminal. On success, validate every requiredVisionPartGenerationResultblock:format,oeNumber,coverage,basis,synthetic,disclosure,finish,part,dimensions,materials,paintableSurface,baseModel,views,identifierProtection,limitationsandgeneratedAt. Copy every requiredviews[].imageUrlinto the customer workflow and verifyviews[].sha256. WhenbaseModel.status=generatedandbaseModel.modelUrlis non-null, process that asset too and verifybaseModel.sha256when the field is present. - For a vehicle listing, only when the Client already owns a valid
tapiId, callPOST /vehicles/{tapiId}/listing(composeVehicleListing) with{quality?, language?, notes?}and anIdempotency-Key.languageisde,enorfr. Persistformat,tapiId,language,title,description,highlights,documentedEquipmentCountandtruncated.
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
- Stop or request review for
unresolved,ambiguousorinvalidnormalization.valid=truealone proves syntax, not existence. - A base-part
404or ambiguous422blocks the automatic enrichment chain. - In a validated
200, treat an empty fitment list as “no assignments returned”, not as global negative proof. Do not manufacture additional assignments locally. UsePOST /vin/cart-checkfor a specific VIN. - Validate
references[]as closed{manufacturer, numbers}groups andreferenceNumbersas the closed{oe_oem_reference_numbers}object. Do not infer direction, interchangeability or fitment from either reference view, and do not derive either view fromreplacementChain. - Read a zero price range through
result:no_listings_foundmeans no listing was usable, not that the part is worth nothing. A range is only evidenced whenresultispriced. If the evaluation could not be carried out at all, the endpoint answers with an error status rather than zeros. The returned currency is a filter, not evidence of conversion. - Use the returned SEO content and live-availability metadata as publication gates. A successful response still requires editorial review before publication.
- Every generated view is synthetic. Retain
basis,disclosure, identity-verification flags,verificationConfidence, skipped angles and limitations with the asset. Do not claim that it depicts the actual stock item's wear or condition. - Use idempotency for image-generation and vehicle-listing POSTs. GET normalization, base data, price, SEO, translation and job polling must not receive the header.
- Run the complete item flow under one tenant key. The idempotency namespace and
tapiIdscope are per Client.
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
matchednormalization proceeds with the returnednormalizedOeNumber; all other statuses stop before the base-part call.- The adapter accepts both populated and empty
fitmentlists, keeps everycriteria[]value with itsvehicleTypeKeyand never manufactures assignments locally. - 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. - The price adapter preserves
condition,currency,vehicleType,resultand confidence with the range. - The SEO adapter accepts the documented publication fields, fills the listing title, category and item specifics from
content.ebayTitle,categoryIdanditemSpecifics[], tolerates anullcategoryIdand an emptyitemSpecifics[], and handles optional live-availability metadata separately. - Image generation persists
202before polling, resumes after restart and verifies each downloaded asset againstsha256. - A generated base-model asset is copied to controlled storage and its checksum is verified when present;
generateBaseModel=falsecreates no dependency onmodelUrl. knowledge_onlyplusreferenceImageUrlsis rejected before transmission.- The published image is visibly governed by the returned synthetic disclosure and never labelled as a stock photograph.
- 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
- A unique local stock-unit ID for every physical part and, when applicable, a local vehicle-unit ID.
- Authorised, publicly retrievable label or part-image URLs.
- A configured condition and
vehicleTypefor pricing, and explicit economic assumptions for vehicle evaluation. - A review queue for multiple OCR candidates, ambiguous normalization, incomplete VIN parts and recall matches.
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
- Call
POST /scanner/label/extract-partnumbers(extractLabelPartNumbers) with{imageUrl, quality?}and anIdempotency-Key. - Read
partNumbers[]; production and sandbox use the same response shape. Preserve every candidate and do not select one only because it appears first. - For each candidate, call
GET /parts/oe/normalize(normalizeOeNumber) withoeNumberand an optional manufacturer hint. Proceed only with a confirmedmatchedresult. - Call
GET /parts/oe/{oeNumber}(getOePart) for the confirmed normalized number. StoretapiGenArt,vdi[],part,fitment,replacementChain,referencesandreferenceNumbersas distinct reference data linked to the physical stock unit. Preservepart.nameas the returned string ornull. TreattapiGenArt: nullandvdi: []as independent missing enrichments, not as a failed base lookup. Keepcriteria[]attached to its opaquevehicleTypeKey.fitmentcontains 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. - Load the classification catalogue with
GET /vdi(getVdiCatalog). Prefer the unpaginated response when feasible. If paging is required, uselimitfrom 1 to 500, advanceoffsetbypage.returned, and stop onpage.hasMore=false. Version the local index bystand.catalogVersion. This catalogue does not return part mappings; use only VDI codes already returned bygetOePartor by a successful VIN-parts position for the link. - Optionally call
GET /parts/oe/{oeNumber}/aftermarket-references(getOeAftermarketReferences). These are reference records, not interchange or fitment guarantees. Prefer an unpaginated response; if paging, usepage.returnedandpage.hasMore. - Optionally call
GET /parts/oe/{oeNumber}/price(getOePrice) with explicitcondition,currencyandvehicleType. - For cluster-level market evidence, call
GET /parts/intelligence(resolvePartIntelligence) with the confirmed OE number, explicit market dimensions and its requiredIdempotency-Key. On202, persist and poll onlyGET /parts/intelligence/jobs/{jobId}(getPartIntelligenceJob). On completion, keep the returnedclusterId,clusterVersion, scope and snapshot metadata together. - With a known cluster, read current Intelligence through
GET /parts/clusters/{clusterId}/intelligence(getPartClusterIntelligence) or materialized snapshots throughGET /parts/clusters/{clusterId}/history(getPartClusterIntelligenceHistory). Both require their own idempotency key; history also requiresfromandtoand never starts a job. - For recall context, batch one to 100 stock positions into
POST /recalls/parts(matchPartRecalls). Send each as{oeNumber, reference, vehicle?}wherereferenceis the local stock-unit key, and use anIdempotency-Key. Map results by returnedreferenceand preserve register data versions and the scope notice.
Vehicle-level dismantling evaluation
- Call
GET /vin/{vin}/economic-evaluation(getVinEconomicEvaluation) with a known full VIN and the documented business parameters such ascountry,condition,vehicleType,maxPricedParts,recoveryRateandcostPerPart. - On
200, persist the result. On202, persist the job and pollGET /vin/economic-evaluation/jobs/{jobId}(getVinEconomicEvaluationJob). - Consume the echoed
assumptions,coverage,revenuePotential,purchaseRecommendationand rankedpartstogether. PreservewarningsandliveAvailabilitywhen present; both are optional. Incomplete coverage makes the totals a lower bound. - If the workflow needs the underlying vehicle-related parts without an economic model, call
GET /vin/{vin}/parts(getPartsByVin) and follow its200/202branch viaGET /vin/parts/jobs/{jobId}(getVinPartsJob). RetainmatchLevelandmissingCategories. - When cluster-based valuation is required, call
POST /vin/economic-intelligence(createVinEconomicIntelligence) with VIN, market country, currency, condition and a separateIdempotency-Key. On202, poll onlyGET /vin/economic-intelligence/jobs/{jobId}(getVinEconomicIntelligenceJob). Keep finalevaluationId, 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
- OCR output is a candidate set. Require normalization and, where business risk warrants it, human confirmation against the physical label.
- Treat normalization
ambiguousandunresolvedas review states. Do not join onlookupKeyas if it were a confirmed OE number. - An empty VDI list means no confirmed mapping. An empty replacement chain means no documented replacement. Neither permits a locally invented classification.
- Because the aftermarket response has conflicting wording around
countand paginated list length, do not usecount == aftermarketReferences.lengthas a paging invariant. Usepage.returnedandpage.hasMore, or omit pagination. - Validate every VIN-parts success before consuming it. A response missing expected public fields must enter quarantine rather than producing an incomplete stock record.
- Recall results are series-level information as of the returned register data version. They do not prove whether a specific physical vehicle or part is affected or remediated.
- Economic output is valid only with its returned assumptions and coverage; retain and apply any
warningsthat are present. Do not compare evaluations that used different dimensions without normalising them in the inventory system. - Use idempotency wherever the selected operation declares it. The three Intelligence data GETs require the header; their status polls do not. Serialize calls per tenant Client, including polls.
- Keep
observedSupply,verifiedSalesandinternalUsageseparate. Supply listings are not sales. Block automated pricing, dismantling or acquisition whenpublishable=false, evidence is insufficient, or confidence violates the configured review threshold.
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
- The adapter accepts both declared label-response property names and preserves each candidate as a separate value.
- Two OCR candidates remain separate until each normalization result is evaluated.
- A VDI catalogue refresh is keyed by
catalogVersion; an OE mapping is never inferred from catalogue content. - Unpaginated and paginated classification loads produce the same ordered entry set; pagination stops by
page.hasMore. - Recall batch responses map back to duplicate OE numbers by unique local
reference, not by number alone. - A
202economic job resumes after restart, and a nested failed status never creates a recommendation. - A response with incomplete coverage is labelled as a lower bound and cannot pass an automatic purchase threshold without the configured review rule.
- Two tenants using the same stock-unit string remain isolated by Client key.
- A resolver
202survives restart and polls only the part-Intelligence status route; itsjobIdfails safely on the VIN status route. - History never creates a job or a synthetic missing point, and cluster version, snapshot ID, scope and
asOfremain attached to every stored point. - Observed-supply counts never populate verified-sale counts, and
publishable=falsecannot trigger automated pricing or acquisition. - Final VIN polling yields an
evaluationIdequal to the precedingjobIdandrequestIdin 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
- A local intake ID for the physical vehicle.
- An authorised public
fileUrland, optionally, one to five authorisedphotoUrlsof the same vehicle. - A two-letter
countryand an explicit decision whether holder data is necessary. Default toincludeOwner=false. - A tenant Client key and an idempotency key persisted before submission.
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
- Call
POST /vehicles/intake(intakeVehicle) with{fileUrl, photoUrls?, country?, includeOwner?}and anIdempotency-Key. - A successful intake is synchronous HTTP
200; this operation has no202branch. Validateformat,registrationDocument,vin,provider,tapiId,vehicle,conditionReport,componentsandcomplete. - Evaluate each component independently:
components.registrationDocumentisdeliveredon every successful response.components.vehicleisdelivered,vin_not_readableorunavailable.components.conditionReportisdelivered,failedornot_requested.complete=trueonly when every requested component was delivered.
- If
tapiIdis non-null, optionally callGET /vehicles/{tapiId}(getVehicleByTapiId) to retrieve the technical vehicle-type record. - If a new or larger photo set must be assessed independently, call
POST /vision/condition-report(reportVehicleCondition) with one to eightimageUrls, optionalqualityand a newIdempotency-Key. Preservegradable,grade,reason, all eight zones,limitations,imageCountandvisualOnly. - If VIN is non-null, optionally call
GET /recalls/vehicles/{vin}(matchVehicleRecalls) and preserve measures, register versions and the scope notice. - If
tapiIdis non-null and publication text is required, callPOST /vehicles/{tapiId}/listing(composeVehicleListing) with{quality?, language?, notes?}and a separateIdempotency-Key;languageisde,enorfr.
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
- Verify that all photo URLs refer to the same vehicle before calling the API.
- Do not request holder data unless the intake purpose requires it. When
includeOwner=false, keep the returned owner block null and do not enrich it elsewhere. - A successful HTTP
200can still be a partial vehicle file. Never require non-nullvin,tapiId,vehicleorconditionReportwithout checkingcomponents. visualOnly=truemeans the condition report is not an expert appraisal, measurement or functional test. A null grade withgradable=falseis a valid outcome; retainreasonandlimitations.- Use separate idempotency keys for intake, a later condition report and listing generation. Retry the same timed-out write with its original key and identical body.
- Use the same tenant Client for the intake and every
tapiIdcontinuation. A404from a continuation can mean the reference is unknown or foreign; do not copy data across tenants.
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
- A complete sandbox intake validates all required response fields and links the local intake ID, VIN and
tapiIdwithout conflating them. - A fixture with
components.vehicle=vin_not_readablepersists the document result, leaves vehicle fields nullable and does not call VIN-dependent endpoints. - A fixture with
components.conditionReport=failedrecords a partial success and does not discard delivered document or vehicle data. includeOwner=falsenever produces a locally populated owner record.- A condition report with
gradable=falseandgrade=nullenters review and preserves all limitations. - Replaying the intake key produces one local intake result and recognises the idempotent response headers.
- A
tapiIdobtained 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
- A Master client API key kept in a privileged secret store and unavailable to normal business-call workers.
- A durable tenant registry with a unique local tenant ID and immutable
externalReferencematching^[A-Za-z0-9._:-]{1,80}$. - A contractually supplied allowlist of valid
endpointKeys. OpenAPI does not publish a complete operation-to-endpoint-key mapping, so middleware configuration must not derive one from paths. - A per-Client queue, idempotency ledger, async-job store, secret manager and audit log.
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
- Reserve the local
externalReferencein a pending provisioning record before any network call. - With the master key, call
POST /client/partner-workspaces(createPartnerWorkspace) with noIdempotency-Key. Send:- required
name,externalReferenceand one to 50 contract-approvedendpointKeys; - optional
sponsorshipandapplicationLabel. - Omit
rateLimitsin this call: OpenAPI exposes that array but does not define its item fields. Apply limits through the typed operation in step 4.
- required
- On
201, validate thatworkspace.idandworkspace.externalReferenceare present before activating the tenant; the top-levelworkspaceis 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 rawapiKey.key,allowlistandsponsorshipGrant. TheapiKeyobject'sid,label,keyandcreatedAtare required. Never write the raw key to logs or ordinary database columns. - 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 ofworkspace.id. Use the Master client key,{rateLimits}and anIdempotency-Key. EachRateLimitInputsuppliesendpointKey,windowSeconds,maxRequests, optionalapiKeyIdand optionalisActive. - If sponsorship terms require a later explicit grant, call
PUT /client/sponsorship-grants/{grantReference}(upsertSponsorshipGrant) with the Master client key, a stablegrantReference, an explicitly sourcedbeneficiaryClientId, approvedendpointKeysand optional contract fields. Use anIdempotency-Key; do not inferbeneficiaryClientIdfromworkspace.id. - 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 anIdempotency-Key.
Runtime routing
- Authenticate the middleware caller and resolve exactly one local tenant record.
- Reject any requested capability outside that Tenant's local allowlist before selecting a tapinomahub path.
- 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.
- 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.
- Apply the shared status/error policy. Persist a
202before releasing the Client queue; schedule the documented poll through the same per-Client queue and matching tenant key. - 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
- 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 sendIdempotency-Key. Before cutover, requireapiKey.id,apiKey.label,apiKey.keyandapiKey.createdAt; neither the response wrapper norApiKeyCreateResponsecurrently marks these fields as required. If any is missing, enterreconciliation_requiredand do not repeat the call automatically. Store a complete raw key once as for initial provisioning. - Move traffic to the new secret only after a successful authentication test such as
GET /ping(getSystemStatus). - Deactivate a Tenant with
PATCH /client/users/{clientId}(updateClientUser) only for an explicitly established Client ID and{isActive:false}using anIdempotency-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
- Treat the API key as the effective tapinomahub Client selector. Never permit a caller to supply or override
X-Api-Key. - Enforce allowlisted operation IDs and paths locally; do not operate as a transparent arbitrary-path proxy.
- Provisioning and additional-key creation are not replay-safe. On timeout or lost response, mark provisioning
reconciliation_requiredand do not call again automatically. A duplicateexternalReferenceprevents a second partner workspace, but the public contract has no lookup-by-external-reference or raw-key recovery operation. RateLimitInputrequiresendpointKey,windowSecondsandmaxRequestsin its schema. Always send all three even though descriptions mention defaults. Validate allowed endpoint keys and windows against supplied contract configuration.- On
429 client_request_in_progress, retain the same tenant job and fix or drain its Client queue. Do not move the call to another tenant key. - On idempotency
409, apply the shared exact branches. Idempotency keys are per Client, so the same textual value in two Clients is not a cross-tenant correlation key. - Keep master administration, tenant business calls and beneficiary billing-mode changes in separate credential scopes.
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
- Provisioning returns one Client and stores the one-time raw key only in the secret manager; logs and database exports contain no secret.
- A workspace success missing
workspace.idorworkspace.externalReferenceand an additional-key success missing raw key material both enter reconciliation rather than activation. - Provisioning omits untyped
rateLimits; the subsequent typed replacement call persists the returned limit set. - An uncertain provisioning response enters reconciliation and is not automatically repeated.
- A caller cannot override its resolved Client key or invoke an operation outside the local allowlist.
- Two tenants run in parallel, while two requests for one Client execute serially.
- A
202job remains tenant-partitioned, survives restart and polls with the same Client key. - Every operation declaring idempotency has a durable pre-dispatch record; Intelligence data GETs carry the header and GET status polls do not.
409 idempotency_key_conflict,409 idempotency_request_in_progressand409 idempotency_result_unavailablereach distinct middleware states.- 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. - 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.