{"openapi":"3.1.0","info":{"title":"Vectelos API","description":"Enterprise Tariff & Customs Compliance API Powered by Google Gemini Multimodal Intelligence","version":"1.0.0"},"paths":{"/v1/auth/register":{"post":{"tags":["Authentication & API Keys"],"summary":"Register User","operationId":"register_user_v1_auth_register_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/RegisterRequest"}}},"required":true},"responses":{"201":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AuthResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/v1/auth/login":{"post":{"tags":["Authentication & API Keys"],"summary":"Login User","operationId":"login_user_v1_auth_login_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/LoginRequest"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AuthResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/v1/auth/verify/request":{"post":{"tags":["Authentication & API Keys"],"summary":"Request Email Verification","description":"Re-send the verification link to the signed-in account's own address.\n\nSession-authenticated: you can only ask us to (re)send to the inbox on the\naccount you are already signed in to, so this cannot be pointed at someone\nelse's address. Idempotent — asking when already verified just says so.","operationId":"request_email_verification_v1_auth_verify_request_post","parameters":[{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Bearer <JWT session token>","title":"Authorization"},"description":"Bearer <JWT session token>"},{"name":"vx_session","in":"cookie","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Vx Session"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/v1/auth/verify/confirm":{"post":{"tags":["Authentication & API Keys"],"summary":"Confirm Email Verification","description":"Mark an account's email verified from the link we sent. Unauthenticated —\nthe signed token is the proof. Clicking a valid link twice is not an error.","operationId":"confirm_email_verification_v1_auth_verify_confirm_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ConfirmVerificationRequest"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/v1/auth/password/forgot":{"post":{"tags":["Authentication & API Keys"],"summary":"Forgot Password","description":"Start a password reset. Always answers 202 with the same message whether\nor not an account exists, so this cannot be used to discover which addresses\nare registered. When the account does exist, a single-use reset link is\nemailed.","operationId":"forgot_password_v1_auth_password_forgot_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ForgotPasswordRequest"}}},"required":true},"responses":{"202":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/v1/auth/password/reset":{"post":{"tags":["Authentication & API Keys"],"summary":"Reset Password","description":"Set a new password from a reset link. Unauthenticated — the signed token\nis the proof. The link is single-use: it carries a fingerprint of the\npassword hash it was minted against, and is refused once that hash changes,\nso a used or stale link cannot reset the password again.","operationId":"reset_password_v1_auth_password_reset_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ResetPasswordRequest"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/v1/auth/keys":{"post":{"tags":["Authentication & API Keys"],"summary":"Generate New Api Key","operationId":"generate_new_api_key_v1_auth_keys_post","parameters":[{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Bearer <JWT session token>","title":"Authorization"},"description":"Bearer <JWT session token>"},{"name":"vx_session","in":"cookie","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Vx Session"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateKeyRequest"}}}},"responses":{"201":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/KeyResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"get":{"tags":["Authentication & API Keys"],"summary":"List Api Keys","description":"List this tenant's API keys. Only the prefix is ever returned -- the raw\nkey is shown once, at creation, and only its SHA-256 digest is stored.","operationId":"list_api_keys_v1_auth_keys_get","parameters":[{"name":"include_revoked","in":"query","required":false,"schema":{"type":"boolean","default":false,"title":"Include Revoked"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Bearer <JWT session token>","title":"Authorization"},"description":"Bearer <JWT session token>"},{"name":"vx_session","in":"cookie","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Vx Session"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/v1/auth/me":{"get":{"tags":["Authentication & API Keys"],"summary":"Get Current Profile","operationId":"get_current_profile_v1_auth_me_get","parameters":[{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Bearer <JWT session token>","title":"Authorization"},"description":"Bearer <JWT session token>"},{"name":"vx_session","in":"cookie","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Vx Session"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/v1/auth/keys/{key_id}":{"delete":{"tags":["Authentication & API Keys"],"summary":"Revoke Key","description":"Revoke one API key. It stops authenticating immediately.\n\nUntil this existed there was no way to kill a leaked key short of editing\nFirestore by hand -- and a `vt_live_` key pasted into a customer's CI log is\nthe ordinary way that happens, not an exotic one.","operationId":"revoke_key_v1_auth_keys__key_id__delete","parameters":[{"name":"key_id","in":"path","required":true,"schema":{"type":"string","title":"Key Id"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Bearer <JWT session token>","title":"Authorization"},"description":"Bearer <JWT session token>"},{"name":"vx_session","in":"cookie","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Vx Session"}}],"responses":{"204":{"description":"Successful Response"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/v1/auth/logout":{"post":{"tags":["Authentication & API Keys"],"summary":"Sign Out","description":"Sign out. This ends EVERY session on the account, not only this one.\n\nA JWT cannot be recalled once signed, so \"sign out\" has to mean invalidating\nthe tokens rather than forgetting one on the client. Each token carries the\nepoch it was minted under; raising the account's epoch refuses all of them\nat the next request.\n\nThat it signs out everywhere is deliberate, and said plainly here because it\nis not what \"log out\" usually means. The reason to reach for this is a token\nyou believe someone else has, and in that case ending only the session in\nyour own browser is precisely the wrong outcome.\n\nAPI keys are untouched -- they have their own revocation, and an ERP should\nnot drop because a person signed out of the portal.","operationId":"sign_out_v1_auth_logout_post","parameters":[{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Bearer <JWT session token>","title":"Authorization"},"description":"Bearer <JWT session token>"},{"name":"vx_session","in":"cookie","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Vx Session"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/v1/auth/webhook-secret":{"post":{"tags":["Authentication & API Keys"],"summary":"Rotate Tenant Webhook Secret","description":"Issue a fresh webhook signing secret. Payloads sent after this are signed\nwith the new value, so update the receiver first.","operationId":"rotate_tenant_webhook_secret_v1_auth_webhook_secret_post","parameters":[{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Bearer <JWT session token>","title":"Authorization"},"description":"Bearer <JWT session token>"},{"name":"vx_session","in":"cookie","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Vx Session"}}],"responses":{"201":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/v1/auth/upgrade":{"post":{"tags":["Authentication & API Keys"],"summary":"Change Plan","description":"Retained only for downgrading to FREE. Paid tiers are granted exclusively by\nthe Stripe webhook after a completed Checkout — POST /v1/billing/checkout.","operationId":"change_plan_v1_auth_upgrade_post","deprecated":true,"parameters":[{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Bearer <JWT session token>","title":"Authorization"},"description":"Bearer <JWT session token>"},{"name":"vx_session","in":"cookie","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Vx Session"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/PlanChangeRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/v1/documents/analyze":{"post":{"tags":["Document Tariff Analysis & Auditing"],"summary":"Analyze Document","operationId":"analyze_document_v1_documents_analyze_post","parameters":[{"name":"Idempotency-Key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Retry the same submission safely. Without one, an identical file from the same account within a minute is treated as a double-tap.","title":"Idempotency-Key"},"description":"Retry the same submission safely. Without one, an identical file from the same account within a minute is treated as a double-tap."},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Bearer <vt_live_... or JWT>","title":"Authorization"},"description":"Bearer <vt_live_... or JWT>"},{"name":"vx_session","in":"cookie","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Vx Session"}}],"requestBody":{"required":true,"content":{"multipart/form-data":{"schema":{"$ref":"#/components/schemas/Body_analyze_document_v1_documents_analyze_post"}}}},"responses":{"202":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/v1/documents/audit":{"post":{"tags":["Document Tariff Analysis & Auditing"],"summary":"Audit Quote Vs Invoice","operationId":"audit_quote_vs_invoice_v1_documents_audit_post","parameters":[{"name":"Idempotency-Key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Retry-safe submission key. Honoured for 24 hours.","title":"Idempotency-Key"},"description":"Retry-safe submission key. Honoured for 24 hours."},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Bearer <vt_live_... or JWT>","title":"Authorization"},"description":"Bearer <vt_live_... or JWT>"},{"name":"vx_session","in":"cookie","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Vx Session"}}],"requestBody":{"required":true,"content":{"multipart/form-data":{"schema":{"$ref":"#/components/schemas/Body_audit_quote_vs_invoice_v1_documents_audit_post"}}}},"responses":{"202":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/v1/jobs/{job_id}":{"get":{"tags":["Async Job Status & Results"],"summary":"Get Job Status","description":"Polls the status and complete calculated result payload for a document analysis job.\nEliminates the requirement for ngrok / open firewall webhooks.","operationId":"get_job_status_v1_jobs__job_id__get","parameters":[{"name":"job_id","in":"path","required":true,"schema":{"type":"string","title":"Job Id"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Bearer <vt_live_... or vt_test_...>","title":"Authorization"},"description":"Bearer <vt_live_... or vt_test_...>"},{"name":"vx_session","in":"cookie","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Vx Session"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/v1/jobs/{job_id}/pdf":{"get":{"tags":["Async Job Status & Results"],"summary":"Download Job Compliance Pdf","description":"Renders the advisory landed-cost estimate as a PDF for the account to hand to\nits customs broker. Not CBP Form 7501 and not a filing.\n\nThe download is single-shot FOR A BROWSER SESSION: once the bytes are on\ntheir way, the stored result is cleared (best-effort — the 30-minute TTL is\nthe backstop). A machine caller keeps its result until the row expires, so\nan ERP that retries a failed download, or fetches the JSON and the PDF in\neither order, is not punished for it. That is decided by the credential that\nOPENED the job, not the one reading it now: a human downloading one PDF from\nthe portal must not clear a batch the ERP is midway through collecting.\nSee docs/api_retention_decisions.md.","operationId":"download_job_compliance_pdf_v1_jobs__job_id__pdf_get","parameters":[{"name":"job_id","in":"path","required":true,"schema":{"type":"string","title":"Job Id"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Bearer <JWT session token>","title":"Authorization"},"description":"Bearer <JWT session token>"},{"name":"vx_session","in":"cookie","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Vx Session"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/v1/jobs/{job_id}/corrections":{"post":{"tags":["Async Job Status & Results"],"summary":"Correct A Line","description":"Apply a reader's classification to one line, and recompute from it.\n\nWHY THIS IS JOB-SCOPED AND SERVER-SIDE. The PDF is rendered from the STORED\nreport, so a correction applied only in the browser would leave the download\n-- the artefact that actually goes to a broker -- still carrying the code the\nreader has just told us is wrong. Recomputing here fixes the table, a later\npoll, and the PDF in one write.\n\nThe client sends a CODE, never a computed line. Everything else is derived\nfrom the schedule on this side, so a correction cannot be used to write\narbitrary duty figures into a report that carries our name.\n\nWHAT IT DOES NOT DO. It does not decide the reader is right. The line is\nstamped `reader_supplied`, carries no confidence score, and is marked\nwherever it is displayed or printed. We recompute arithmetic on their\nassertion; we do not adopt it.","operationId":"correct_a_line_v1_jobs__job_id__corrections_post","parameters":[{"name":"job_id","in":"path","required":true,"schema":{"type":"string","title":"Job Id"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Bearer <JWT session token>","title":"Authorization"},"description":"Bearer <JWT session token>"},{"name":"vx_session","in":"cookie","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Vx Session"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/LineCorrection"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/v1/billing/plans":{"get":{"tags":["Billing & Subscriptions"],"summary":"List Plans","description":"Public price list, so the portal never hardcodes amounts.","operationId":"list_plans_v1_billing_plans_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/v1/billing/checkout":{"post":{"tags":["Billing & Subscriptions"],"summary":"Start Checkout","operationId":"start_checkout_v1_billing_checkout_post","parameters":[{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Bearer <JWT session token>","title":"Authorization"},"description":"Bearer <JWT session token>"},{"name":"vx_session","in":"cookie","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Vx Session"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CheckoutRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CheckoutResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/v1/billing/portal":{"post":{"tags":["Billing & Subscriptions"],"summary":"Open Billing Portal","description":"Stripe-hosted portal for card changes, invoices and cancellation.","operationId":"open_billing_portal_v1_billing_portal_post","parameters":[{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Bearer <JWT session token>","title":"Authorization"},"description":"Bearer <JWT session token>"},{"name":"vx_session","in":"cookie","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Vx Session"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PortalResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/v1/billing/subscription":{"get":{"tags":["Billing & Subscriptions"],"summary":"Subscription Status","description":"Current entitlement as Vectelos sees it (updated by webhook).","operationId":"subscription_status_v1_billing_subscription_get","parameters":[{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Bearer <JWT session token>","title":"Authorization"},"description":"Bearer <JWT session token>"},{"name":"vx_session","in":"cookie","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Vx Session"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/v1/billing/waitlist":{"post":{"tags":["Billing & Subscriptions"],"summary":"Join Waitlist","operationId":"join_waitlist_v1_billing_waitlist_post","parameters":[{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Bearer <JWT session token>","title":"Authorization"},"description":"Bearer <JWT session token>"},{"name":"vx_session","in":"cookie","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Vx Session"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/WaitlistRequest"}}}},"responses":{"201":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/v1/feedback":{"post":{"tags":["Accuracy Feedback"],"summary":"Submit Line Feedback","description":"Record one signal from one reader.\n\nAuthenticated so only signed-in users can post, and then the identity is\ndropped: `current_user` is not passed to `record_line_feedback`, so the\nfeedback row itself carries nothing that ties it to an account or a shipment.\n\nThe one deliberate exception: a \"flagged\" signal bumps an opaque per-tenant\ncounter in `vectelos_activation` (via `note_thumbs_down`) so a marketing\ncohort's flag rate is visible. That store holds counts only — no feedback\ncontent, no HTS code, nothing joinable back to what was flagged. See\n`GTM_plan.md` §4.4.","operationId":"submit_line_feedback_v1_feedback_post","parameters":[{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Bearer <JWT session token>","title":"Authorization"},"description":"Bearer <JWT session token>"},{"name":"vx_session","in":"cookie","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Vx Session"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/LineFeedback"}}}},"responses":{"201":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/FeedbackAck"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/v1/account/sku-memory":{"delete":{"tags":["Account"],"summary":"Forget My Skus","description":"Forget every classification this account has confirmed.\n\nWHY THIS EXISTS AT ALL. SKU memory keeps a per-tenant record, for a year,\nof the tariff lines an importer has confirmed. The description is never\nstored -- the key is a digest -- but \"you cannot read it\" is a weaker\npromise than \"you can delete it\", and a permanent record the customer\ncannot clear is a different product from Zero Doc Store even when hashed.\nThe owner made reversibility a condition of choosing reuse (2026-09-15).\n\nA purge is not destructive to the customer's work: the next analysis\nclassifies every line with the engine as it did before, and the picker\noffers the alternatives again. They lose the shortcut, not the answer.","operationId":"forget_my_skus_v1_account_sku_memory_delete","parameters":[{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Bearer <JWT session token>","title":"Authorization"},"description":"Bearer <JWT session token>"},{"name":"vx_session","in":"cookie","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Vx Session"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/health":{"get":{"tags":["Health"],"summary":"Health Check","operationId":"health_check_health_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/":{"get":{"tags":["Root"],"summary":"Root","operationId":"root__get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}}},"components":{"schemas":{"Attribution":{"properties":{"utm_source":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Utm Source"},"utm_medium":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Utm Medium"},"utm_campaign":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Utm Campaign"},"utm_content":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Utm Content"},"utm_term":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Utm Term"},"gclid":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Gclid"},"landing_path":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Landing Path"},"consent":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Consent"}},"type":"object","title":"Attribution","description":"Where a sign-up came from. Sent by the portal, forwarded from the landing\npage's `.vectelos.com` cookie / URL params. The API treats every field as\nopaque analytics input — it is never used for authorisation. See\n`GTM_plan.md` §4.3."},"AuthResponse":{"properties":{"user_id":{"type":"string","title":"User Id"},"email":{"type":"string","title":"Email"},"plan":{"type":"string","title":"Plan"},"credits":{"type":"integer","title":"Credits"},"access_token":{"type":"string","title":"Access Token"},"token_type":{"type":"string","title":"Token Type","default":"bearer"}},"type":"object","required":["user_id","email","plan","credits","access_token"],"title":"AuthResponse"},"Body_analyze_document_v1_documents_analyze_post":{"properties":{"file":{"type":"string","contentMediaType":"application/octet-stream","title":"File","description":"Commercial document (PDF, PNG, JPG, WEBP)"},"webhook_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Webhook Url","description":"Optional HTTPS callback URL"},"as_of":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"As Of","description":"Date of entry to compute for (YYYY-MM-DD). Defaults to today."}},"type":"object","required":["file"],"title":"Body_analyze_document_v1_documents_analyze_post"},"Body_audit_quote_vs_invoice_v1_documents_audit_post":{"properties":{"quotation_file":{"type":"string","contentMediaType":"application/octet-stream","title":"Quotation File","description":"Pre-shipment Quotation or Proforma Invoice (PDF/Image)"},"commercial_invoice_file":{"type":"string","contentMediaType":"application/octet-stream","title":"Commercial Invoice File","description":"Final Commercial Invoice (PDF/Image)"},"webhook_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Webhook Url","description":"Optional HTTPS callback URL"}},"type":"object","required":["quotation_file","commercial_invoice_file"],"title":"Body_audit_quote_vs_invoice_v1_documents_audit_post"},"CheckoutRequest":{"properties":{"plan":{"type":"string","title":"Plan","description":"PRO or BUSINESS"}},"type":"object","required":["plan"],"title":"CheckoutRequest"},"CheckoutResponse":{"properties":{"checkout_url":{"type":"string","title":"Checkout Url"},"session_id":{"type":"string","title":"Session Id"},"plan":{"type":"string","title":"Plan"}},"type":"object","required":["checkout_url","session_id","plan"],"title":"CheckoutResponse"},"ConfirmVerificationRequest":{"properties":{"token":{"type":"string","title":"Token"}},"type":"object","required":["token"],"title":"ConfirmVerificationRequest"},"CreateKeyRequest":{"properties":{"name":{"type":"string","maxLength":120,"title":"Name","default":"Default Key"},"is_test":{"type":"boolean","title":"Is Test","default":false}},"type":"object","title":"CreateKeyRequest"},"FeedbackAck":{"properties":{"recorded":{"type":"boolean","title":"Recorded"},"description_included":{"type":"boolean","title":"Description Included","description":"Echoes back whether the description was stored, so the UI can tell the reader what actually left their browser"},"message":{"type":"string","title":"Message"}},"type":"object","required":["recorded","description_included","message"],"title":"FeedbackAck"},"ForgotPasswordRequest":{"properties":{"email":{"type":"string","format":"email","title":"Email"}},"type":"object","required":["email"],"title":"ForgotPasswordRequest"},"HTTPValidationError":{"properties":{"detail":{"items":{"$ref":"#/components/schemas/ValidationError"},"type":"array","title":"Detail"}},"type":"object","title":"HTTPValidationError"},"KeyResponse":{"properties":{"id":{"type":"string","title":"Id"},"name":{"type":"string","title":"Name"},"api_key":{"type":"string","title":"Api Key","description":"Raw API key (only shown once upon generation)"},"created_at":{"type":"string","title":"Created At"}},"type":"object","required":["id","name","api_key","created_at"],"title":"KeyResponse"},"LineCorrection":{"properties":{"item_index":{"type":"integer","title":"Item Index","description":"Which line the reviewer is editing"},"recompute_from_schedule":{"type":"boolean","title":"Recompute From Schedule","description":"Set by the clarification picker, where the reader CHOSE a tariff line rather than asserting a rate: recompute duty and trade remedies from the published schedule for `hts_code`. Default false keeps the settled contract that a bare `hts_code` is a LABEL and does not touch duty. Ignored when `tariff_rate_pct` is supplied -- an explicit rate is always the reviewer's assertion and always wins.","default":false},"hts_code":{"anyOf":[{"type":"string","maxLength":32},{"type":"null"}],"title":"Hts Code","description":"HTS code the reviewer assigns (a label; their rate governs the duty)"},"description":{"anyOf":[{"type":"string","maxLength":2000},{"type":"null"}],"title":"Description","description":"Reviewer's amended description / note"},"quantity":{"anyOf":[{"type":"number","minimum":0.0},{"type":"null"}],"title":"Quantity"},"total_value_usd":{"anyOf":[{"type":"number","minimum":0.0},{"type":"null"}],"title":"Total Value Usd","description":"Reviewer's customs value for the line"},"tariff_rate_pct":{"anyOf":[{"type":"number","maximum":1000.0,"minimum":0.0},{"type":"null"}],"title":"Tariff Rate Pct","description":"Ad-valorem rate the reviewer applies; duty = value × rate"},"quantity_unit":{"anyOf":[{"type":"string","maxLength":32},{"type":"null"}],"title":"Quantity Unit","description":"Unit of the quantity on this line (kg, each, liter, pr, doz, bbl...). Supplied for a specific or compound rate whose duty could not be computed because the invoice did not state one. The published rate still governs; this does not override it."}},"type":"object","required":["item_index"],"title":"LineCorrection","description":"A reviewer's edit to one line. Every field except item_index is optional;\nwhatever is omitted keeps its current value. The reviewer owns the result —\nthe line is stamped reader-supplied and marked as theirs on the PDF, so the\nold 'we only accept a CODE and derive everything from the schedule' guard is\nrelaxed on purpose (the owner asked for full manual control and understands\nthat changing the HTS/rate is their assertion, not ours).\n\nTHE CAPS BELOW MUST NOT BE TIGHTER THAN WHAT THE ANALYZER EMITS. The editor\npre-fills every field from the stored line, so a cap below the analyzer's own\noutput makes \"open the pencil and press save\" fail on a line the reviewer\nnever even changed. That is what happened: `description` was capped at 500\nwhile `AnalyzedLineItem.description` is unbounded, so a long extracted\ndescription 422'd on a no-op save. Caps are kept (unbounded input reaches the\nPDF renderer) but sized above anything the pipeline produces."},"LineFeedback":{"properties":{"signal":{"type":"string","enum":["flagged","extraction_error","description_confirmed","description_wrong"],"title":"Signal","description":"What the reader is telling us"},"hts_code":{"type":"string","title":"Hts Code","description":"The code the engine selected","default":""},"confidence":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Confidence","description":"Engine confidence, 0-1"},"duty_rate_pct":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Duty Rate Pct","description":"Effective rate applied"},"chapter99_codes":{"items":{"type":"string"},"type":"array","title":"Chapter99 Codes","description":"Remedy headings applied, e.g. 9903.82.02"},"caveats_fired":{"type":"integer","title":"Caveats Fired","description":"How many determinations the engine declined to make","default":0},"engine_reasoning":{"anyOf":[{"type":"string","maxLength":2000},{"type":"null"}],"title":"Engine Reasoning","description":"The engine's own stated reasoning for this classification"},"origin_country":{"anyOf":[{"type":"string","maxLength":2},{"type":"null"}],"title":"Origin Country"},"port_city":{"anyOf":[{"type":"string","maxLength":64},{"type":"null"}],"title":"Port City"},"port_state":{"anyOf":[{"type":"string","maxLength":32},{"type":"null"}],"title":"Port State"},"what_is_wrong":{"anyOf":[{"type":"string","maxLength":64},{"type":"null"}],"title":"What Is Wrong","description":"Which part looks wrong, from a fixed list the UI offers"},"suggested_hts_code":{"anyOf":[{"type":"string","maxLength":13},{"type":"null"}],"title":"Suggested Hts Code"},"comment":{"anyOf":[{"type":"string","maxLength":1000},{"type":"null"}],"title":"Comment"},"include_description":{"type":"boolean","title":"Include Description","description":"True only when the reader chose to send the item description","default":false},"item_description":{"anyOf":[{"type":"string","maxLength":500},{"type":"null"}],"title":"Item Description","description":"Sent only when include_description is true; dropped otherwise"}},"type":"object","required":["signal"],"title":"LineFeedback","description":"One signal from the person reading a report.\n\nZERO DOC STORE. This is the only route by which anything derived from a\ncustomer's document reaches durable storage, and it exists because the\nengine cannot grade itself: a stored HTSUS code is what we decided, not\nwhether we were right. Feedback is the grade.\n\nThree rules hold it to the claim on the site:\n\n  1. Nothing is written unless the reader submits it. We do not sample\n     reports, and no background process reads a stored report.\n  2. No `user_id`, no `job_id`, no tenant identifier is recorded. The\n     request is authenticated so only signed-in users can post, and then\n     who they are is deliberately dropped.\n  3. Money and quantities never travel. Not the entered value, not the duty\n     assessed, not the line total.\n\nWHY THERE IS NO \"THIS IS CORRECT\" SIGNAL. A thumbs-up cannot distinguish\n\"I checked this against my broker's last entry\" from \"I clicked the green\none because the red one asked me questions\" -- but it looks like data, and\nat low volume it would manufacture an accuracy denominator we have not\nearned. Silence is allowed to mean silence. The one exception is\n`description_confirmed`, which answers a specific question about a specific\nline (\"is this roughly what it is?\") and so carries actual information."},"LoginRequest":{"properties":{"email":{"type":"string","format":"email","title":"Email"},"password":{"type":"string","title":"Password"}},"type":"object","required":["email","password"],"title":"LoginRequest"},"PlanChangeRequest":{"properties":{"plan":{"type":"string","title":"Plan","description":"Only FREE is accepted here — paid plans go through Stripe Checkout."}},"type":"object","required":["plan"],"title":"PlanChangeRequest"},"PortalResponse":{"properties":{"portal_url":{"type":"string","title":"Portal Url"}},"type":"object","required":["portal_url"],"title":"PortalResponse"},"RegisterRequest":{"properties":{"email":{"type":"string","format":"email","title":"Email"},"password":{"type":"string","minLength":12,"title":"Password","description":"User password (min 12 characters)"},"attribution":{"anyOf":[{"$ref":"#/components/schemas/Attribution"},{"type":"null"}]}},"type":"object","required":["email","password"],"title":"RegisterRequest"},"ResetPasswordRequest":{"properties":{"token":{"type":"string","title":"Token"},"new_password":{"type":"string","minLength":12,"title":"New Password"}},"type":"object","required":["token","new_password"],"title":"ResetPasswordRequest"},"ValidationError":{"properties":{"loc":{"items":{"anyOf":[{"type":"string"},{"type":"integer"}]},"type":"array","title":"Location"},"msg":{"type":"string","title":"Message"},"type":{"type":"string","title":"Error Type"},"input":{"title":"Input"},"ctx":{"type":"object","title":"Context"}},"type":"object","required":["loc","msg","type"],"title":"ValidationError"},"WaitlistRequest":{"properties":{"plan":{"type":"string","title":"Plan","description":"Which tier the account is interested in"},"volume_note":{"anyOf":[{"type":"string","maxLength":500},{"type":"null"}],"title":"Volume Note","description":"Roughly how many entries a month, in their own words"},"attribution":{"anyOf":[{"$ref":"#/components/schemas/Attribution"},{"type":"null"}]}},"type":"object","required":["plan"],"title":"WaitlistRequest"}}}}