SKU.io · Engineering Research · Unlisted
What it would actually cost and mean to integrate SKU.io with TaxJar, Avalara, Zamp, Numeral or Kintsugi — scored on integration surface, PHP/Laravel fit, domain fit, and commercial terms. Not a feature comparison of the products themselves.
Weighted scores out of 5 — the model and its weights are shown in §05 so you can disagree with them explicitly rather than implicitly.
Only one that survives a NetSuite bake-off. Live PHP SDK, 8.9M installs, global VAT/GST. No marketplace-facilitator primitive — we compensate by pre-classifying.
The only genuine software-platform partnership: sandbox + production, certified badge, recurring commission. No PHP SDK; US + Canada only.
Technically the best fit — and the worst bet. Maintenance mode post-Stripe, SDK stalled 15 months, ERP connectors sunset, price rising steeply.
Best architectural fit and a PHP SDK on 8.2+ — but it's v0.6.2 with 1,197 downloads. Pre-1.0 is not a compliance dependency.
Clean, modern API aimed at Stripe-centric SaaS billing. No PHP SDK, and the "partner program" is a referral commission.
Avalara and TaxJar are tax engines you call. Zamp, Numeral and Kintsugi are managed compliance services that expose an API. That difference decides the integration shape far more than any feature row below.
Note also that four of the five publish "X vs Y" comparison pages about the other four. Every commercial cell in this matrix is marked for confidence and should be re-verified in a sales call before it informs a decision.
The mechanics of the integration itself: what you authenticate with, what you call, what you can test against, and what the vendor is willing to commit to operationally.
| Dimension | Avalara | TaxJar | Zamp | Numeral | Kintsugi |
|---|---|---|---|---|---|
| Category | Tax engine — enterprise | Tax engine — SMB | Managed service + API | Managed service, API-first | Managed service, AI-positioned |
| Authentication | ✓License key / Bearer | ✓Authorization: Token or Bearer | ✓Bearer | ✓Bearer | ~API key |
| Base URL | ✓AvaTax REST v2 | ✓api.taxjar.com/v2 | ✓Zamp API | ✓api.numeralhq.com | ~Kintsugi API |
| Versioning strategy | ✓URL-versioned (v2) | ✓x-api-version date header | ✓X-API-Version date header | ✓X-API-Version — 2024-09-01 default, 2025-05-12 current | ?Not documented |
| Calculate (rate quote) | ✓CreateTransaction uncommitted | ✓POST /v2/taxes | ✓POST /calculations | ✓/tax/calculations | ✓Tax Estimates (read-only, repeatable) |
| Commit (record for filing) | ✓CommitTransaction | ✓POST /v2/transactions/orders | ✓POST /transactions | ✓Transactions | ✓Transactions (permanent liability record) |
| Void / adjust | ✓VoidTransaction, AdjustTransaction | ✓Delete / update order | ?Not documented | ~Refund handling | ?Not documented |
| Refund / credit note maps to our SalesCredit |
✓RefundTransaction | ✓/v2/transactions/refunds | ?Implied by transactions | ~Documented capability | ?Not documented |
| Address validation | ✓ResolveAddress | ✓Address validation endpoint | ✓Address validation endpoint | ?Not surfaced | ✓Required ahead of estimation |
| Lookup granularity | ✓Full address | ✓Address / ZIP | ✓Address, lat/long, postal code | ~Address | ✓Resolved address to local jurisdiction |
| Sandbox environment | ✓Yes | ✓api.sandbox.taxjar.com | ✓Yes + free trial env pre-registered in all 47 remote-seller states | ?Not documented | ~Yes |
| Published rate limit | ?Not published | ~10,000 calls/min (Professional plans) | ✓1,000 req/min | ?Not published | ?Not published |
| Published latency target | ?None | ?None | ✓~400ms p95 | ?None | ?None |
| Idempotency keys | ~Via transaction code reuse | ~Via transaction_id uniqueness | ?Not documented | ?Not documented | ~Keyed on your order ID |
| Webhooks / callbacks | ~Available in platform | ~Limited | ?Not documented | ?Not documented | ?None documented |
All figures pulled live from the Packagist API on 2026-08-20. This section moves the estimate more than any other: the difference between a maintained SDK and writing your own client is roughly a fortnight, and the difference between a stable SDK and a pre-1.0 one is an unbounded maintenance tail.
| Dimension | Avalara | TaxJar | Zamp | Numeral | Kintsugi |
|---|---|---|---|---|---|
| Official PHP SDK | Yes | Yes | No | No | Yes |
| Package | avalara/avataxclient | taxjar/taxjar-php | — | — | kintsugi-tax/tax-platform-sdk |
| Other SDK languages | .NET, Java, Python, Ruby, JS | Ruby, Python, Node, C#, Java, Go | — | Java, Python, Ruby | Python, TypeScript, Java, Ruby |
| Latest version | 26.7.3 | v2.0.1 | — | — | v0.6.2 |
| Last release | 27 Jul 2026 | 8 May 2025 — 15 months ago | — | — | 7 Jul 2026 |
| Total installs | 8,879,886 | 6,848,032 | — | — | 1,197 |
| Released versions | 102 | 26 | — | — | 21 |
| PHP constraint | >=5.5.9 legacy shape — expect untyped array models | ^8.0 | — | — | >=8.2 matches our 8.3 |
| API stability | Stable, 1.0+ | Stable, 2.x | n/a — we own the client | n/a — we own the client | Pre-1.0 — breaking changes expected |
| Client we must build & maintain | Thin wrapper only | Thin wrapper only | Full REST client clean API, ~3–5 days | Full REST client | Thin wrapper + version pinning |
| Laravel ecosystem presence | Community wrappers exist | Community wrappers exist | None | None | None |
This is the section that should decide the choice, because it's the only one where SKU.io's position is unusual. We know the sales channel for every order via sales_orders.sales_channel_id → sales_channels.integration_instance_id → integrations.name. Shopify's native report cannot know this. Avalara's Shopify connector cannot know this. If the provider can't receive that fact, the advantage dies at the API boundary.
| Dimension | Avalara | TaxJar | Zamp | Numeral | Kintsugi |
|---|---|---|---|---|---|
| Per-transaction marketplace-facilitator flag ★ the decisive criterion |
No primitive. Marketplaces are a company-level configuration, not a transaction attribute. isSellerImporterOfRecord is import duty, not facilitator — an expensive thing to misread. |
First class. exemption_type:"marketplace" + provider on /v2/taxes and /v2/transactions. Applies state law as at transaction_date. |
Platform-level. Auto-identifies marketplace transactions, excludes from filing, retains for nexus. Per-transaction field unconfirmed. | Not documented. Presumably via exemptions. | Not documented. Presumably via exemptions. |
| Recognised marketplaces | Manual configuration | Amazon, eBay, Etsy, Walmart built in; others via exemption_type alone | Amazon, eBay, Etsy connectors | ? | Amazon, Shopify, WooCommerce |
| State-and-date-aware facilitator logic | No | Yes — exempt only if that state had a facilitator law on the transaction date | Documented per-state nexus treatment | ? | ? |
| Nexus monitoring API | Yes, enterprise | Economic Nexus Insights — the category blueprint | Yes, with per-state marketplace treatment | Yes | Yes, incl. free tier |
| Product tax codes / taxability | Extensive catalogue | Yes | Single un-paginated tax-codes endpoint | Products resource | Products with categories |
| Exemption certificates | Yes (CertCapture) | Yes | Yes | Yes | Yes, on the customer object |
| Filing & remittance | Yes — Returns | Yes — AutoFile | Yes — fully managed | Yes — managed | Yes |
| Geography | Global — VAT, GST, e-invoicing | US-first | US + Canada only | US + VAT, 32 currencies | US + VAT/GST |
Covers pod-au-1 (fleur, curvy) |
Yes | Limited | No | Partial | Partial |
| Multi-currency | Yes | Limited | USD / CAD | 32 currencies | Yes |
| Vendor-named rollout model | — | — | Calculate-then-post | — | L1 report → L2 estimate — matches our staging exactly |
docs/plans/sales-tax-reporting.md states that economic-nexus thresholds in many states exclude marketplace sales. The real picture is close to an even split and is state-specific: roughly 20+ states include marketplace-facilitated sales in threshold maths (Alaska, California, Connecticut …), roughly 20 exclude them (Alabama, Arizona, Colorado, Florida …), and some (Oklahoma, Pennsylvania) exclude only conditionally on the facilitator having collected.
Phase 3 therefore needs a per-state includes_marketplace_sales attribute on TaxNexusThreshold, not a global exclusion rule. Getting it wrong under-reports exposure across half the country — precisely the failure the feature exists to prevent.
Whether integrating is a cost line or a revenue line. Every cell here is vendor-published and should be treated as a starting position, not a fact.
| Dimension | Avalara | TaxJar | Zamp | Numeral | Kintsugi |
|---|---|---|---|---|---|
| Partner program type | Certified Integration / ISV / OEM-embedded | —Stripe ecosystem | Software-platform partnership | Referral | Integration listing |
| Partner sandbox + production access | Yes, via certification | Self-serve | Yes, explicit | ? | Self-serve |
| Revenue share | ?Not published | — | Recurring commission, no upfront cost | 12-month commission on referrals | ?Not published |
| Certification / directory listing | Certified Integration — 1,400+ listed | — | Zamp Certified | Co-marketing, webinars, blog | Listing |
| White-label / embedded UX | OEM-embedded partner type | — | Embedded registrations, filings, reports | White-label offered | ? |
| Dedicated technical partner support | Preferred Developer program | Standard support | Yes, through integration & launch | Private Slack channel | Standard support |
| Pricing model | Enterprise, modular, quote | Per-transaction tiers | All-inclusive quote, no per-filing fee | Per-event, no monthly minimum | Freemium + per-event |
| Published price points | ~Quote only | ~Rising >100%; legacy pricing ends 1 Oct 2026 | ~~$8.6k–$14.9k/yr mid-market | ~$75/filing · $150/registration | ~Free tier · $75/filing |
| Onboarding friction | Self-serve dev account | Self-serve | Token issued by emailing support | Sales-led | Self-serve + free tier |
| Competitor already integrated | Cin7, NetSuite, Brightpearl | Many | Cin7 | — | — |
| Vendor trajectory | Stable incumbent | Maintenance mode; ERP connectors sunset | Growth-stage | Growth-stage | Early-stage |
Scores 1–5 per dimension, weighted as below. This is a judgement model, not an objective measurement — the weights encode the view that marketplace-facilitator support matters most because it is the only dimension where SKU.io's position is genuinely unusual, and that build cost matters second because it is the most reliably estimable.
| Dimension (weight) | Avalara | TaxJar | Zamp | Numeral | Kintsugi |
|---|---|---|---|---|---|
| Marketplace-facilitator support 25% | 2.0 | 5.0 | 3.0 | 2.0 | 2.5 |
| PHP / Laravel build cost 20% | 5.0 | 3.0 | 2.5 | 2.0 | 3.5 |
| API quality & operability 15% | 4.0 | 4.0 | 4.5 | 4.0 | 3.5 |
| Vendor viability & trajectory 15% | 5.0 | 1.0 | 3.0 | 3.0 | 2.0 |
| Commercial / partnership upside 15% | 3.0 | 1.0 | 5.0 | 2.0 | 2.5 |
| Geographic coverage 10% | 5.0 | 2.0 | 2.0 | 3.0 | 4.0 |
The spread is narrow — 2.55 to 3.80 — which is itself the finding. No option dominates. Avalara wins on everything except the dimension that matters most, and TaxJar wins that one dimension while failing nearly every other. That asymmetry is the argument for an abstraction rather than a bet.
Three genuinely different integrations hide under the single word "integrate". Deciding which one you're buying should precede choosing a vendor.
| Shape | What SKU.io does | What we give up | Rough effort | Viable with |
|---|---|---|---|---|
| A · Push-only report & file |
Keep capturing tax exactly as today; push committed transactions so the provider can file returns. No calculation. | Nothing. Purely additive to the existing PRD, and reversible. | ~3–4 weeksincl. abstraction + one provider | All five |
| B · Calculate-at-order | Call the provider for a rate at order/quote time, then commit. Replaces TaxRateManager for opted-in tenants. |
The latency budget, and the outage failure mode — a third party now sits in order entry. | +2–3 weeksper provider | All five |
| C · Embedded compliance | The provider's registrations / nexus / filing UX is surfaced inside SKU.io. We're the channel, they're the back office. | Control of the finance surface — but gains a revenue line. | Partnership-dependent | Zamp, Numeral (white-label), Kintsugi |
1 · Abstraction before integration. Five vendors, three under five years old, one in maintenance mode, a category consolidating fast. The only irreversible mistake is coupling managers to a vendor SDK. A narrow TaxProviderInterface — calculate · commit · void · refund · validateAddress — costs little and buys optionality.
2 · Ship Avalara first, as Shape A. Send pre-classified transactions: we do the marketplace split on our side, where the channel data lives. Their weakest point becomes our value-add rather than our blocker.
3 · Open the Zamp platform-partner conversation in parallel. Cheap to explore, and the free trial environment allows prototyping before signing. Two questions to settle on the first call: the per-transaction marketplace-facilitator field, and any non-North-America roadmap.
4 · Design for Kintsugi; adopt their L1→L2 sequencing regardless of vendor. Don't lead with TaxJar — copy its interface shape instead. Skip Numeral unless a prospect arrives already on it.
Modules/ module. Every existing module is a source of orders or accounting data; a tax provider is a service the app calls. Belongs in app/Services/Tax/ behind a contract, driven from SalesTaxSettings.App\Contracts namespace has a known unbound-interface trap in this repo — bind in a service provider and add a test that resolves it.>=5.5.9 constraint means untyped array models; wrap in app/Data/ Laravel Data objects at the provider boundary.Model::create() with logic in a manager.TenantAwareJob) on order close / sales-credit creation — never inline in the request. Use the Tracked Job pattern for historical backfills.sales_orders.order_date is UTC; provider transaction_date is jurisdiction-local for state-law purposes. Convert via Helpers::dateUtcToLocal() or transactions land in the wrong filing period at month boundaries.hitlights on Avalara and caviarco on Zamp must both work.TaxRateManager figure, queue the commit, reconcile later.SecretsManagerServiceProvider — config defines avalara.sandbox.* / avalara.production.*, provider resolves to avalara.auth.*. Config::preventStrayRequests() in tests.What this matrix is and isn't standing on.
Verified from primary sources
Weak or unverified — treat as questions, not findings