1. Go to this page and download the library: Download taxora/sdk-php library. Choose the download type require.
2. Extract the ZIP file and open the index.php.
3. Add this code to the index.php.
<?php
require_once('vendor/autoload.php');
/* Start to develop here. Best regards https://php-download.com/ */
taxora / sdk-php example snippets
use Taxora\Sdk\TaxoraClientFactory;
use Taxora\Sdk\Enums\Environment;
$client = TaxoraClientFactory::create(
apiKey: 'YOUR_X_API_KEY',
environment: Environment::SANDBOX // or PRODUCTION
);
// 1️⃣ Authenticate
$client->auth()->login('[email protected]', 'superSecret');
// 2️⃣ Validate a VAT number
$vat = $client->vat()->validate('ATU12345678', 'Example GmbH');
echo $vat->state->value; // valid / invalid
echo $vat->company_name; // Official company name
echo $vat->score; // Overall confidence score (float)
var_dump($vat->has_api_error); // true when the official provider had a technical failure
echo $vat->error_message; // technical provider error details, if returned
echo $vat->next_api_recheck_at; // planned retry timestamp, if returned
foreach ($vat->breakdown ?? [] as $step) {
echo $step->stepName.' gave '.$step->scoreContribution.PHP_EOL;
}
// Optional typed address input for fallback name scoring
$addressInput = new \Taxora\Sdk\ValueObjects\VatValidationAddressInput(
addressLine1: 'Example GmbH',
addressLine2: '2nd Floor',
postalCode: '1010',
city: 'Vienna',
countryCode: 'AT'
);
$vatWithAddress = $client->vat()->validate('ATU12345678', 'John Doe', 'vies', $addressInput);
// 3️⃣ Access company info
$company = $client->company()->get();
echo $company['api_rate_limit'] ?? 'n/a';
echo $company['vat_rate_limit'] ?? 'n/a';
// 4️⃣ Export certificates (returns a VatCertificateExport object)
$export = $client->vat()->certificatesBulkExport('2024-01-01', '2024-12-31');
$pdfZip = $client->vat()->downloadBulkExport($export->exportId);
file_put_contents('certificates.zip', $pdfZip);
// Single lookup
$job = $client->smartEnrichment()->lookup(
'Example Company GmbH',
'AT',
street: 'Main Street 10',
postalCode: '1010',
city: 'Vienna',
);
if ($job->isProcessing()) {
// Resolved asynchronously — poll (or wait for the webhook)
$job = $client->smartEnrichment()->get($job->jobId);
}
$result = $job->result();
echo $result?->status->value; // found | no_vat_exists | not_found
echo $result?->vatNumber; // e.g. ATU12345678
echo $result?->confidence; // 0–100
// Search mode: `default` searches with one AI provider and only consults a second one
// when the first finds nothing. `complex` has two independent providers search from the
// start and puts every number either of them proposes through the tax authority's checks.
// Complex finds companies a single search misses — typically ones whose VAT only appears
// in a website Impressum — and costs more per lookup, so it is opt-in.
$job = $client->smartEnrichment()->lookup(
'Vodafone D2 GmbH',
'DE',
postalCode: '40547',
mode: SmartEnrichmentMode::COMPLEX,
);
// When more than one provider searched, you can see what each one concluded on its own.
// Two of them reporting the same number is the strongest confirmation this layer produces.
foreach ($job->result()?->providerVerdicts ?? [] as $verdict) {
echo $verdict['provider'] . ': ' . ($verdict['vatNumber'] ?? 'nothing') . PHP_EOL;
}
// The API also grades the address you sent. Worth writing back into your own records:
// a `postal_code_city_mismatch` / `postal_code_unassigned` warning means the postal code
// in your source data is wrong, and `derivedPlace` is the area it actually points at.
$quality = $job->result()?->addressQuality;
if ($quality !== null && $quality['warnings'] !== []) {
echo implode(', ', $quality['warnings']) . ' → ' . ($quality['derivedPlace'] ?? '?') . PHP_EOL;
}
// Bulk lookup (always async → webhook + polling). The batch-level mode applies to every
// row; a row carrying its own 'mode' overrides it, so you can pay for `complex` on just
// the rows that need it.
$bulk = $client->smartEnrichment()->bulkLookup([
['companyName' => 'A GmbH', 'country' => 'DE', 'city' => 'Berlin'],
['companyName' => 'B SARL', 'country' => 'FR', 'mode' => 'complex'],
]);
$done = $client->smartEnrichment()->get($bulk->jobId);
foreach ($done->results as $r) {
echo $r->status->value . ': ' . ($r->vatNumber ?? '—') . PHP_EOL;
}
// Or block until the job is done (polls get() every 2s, up to 120s by default;
// throws Taxora\Sdk\Exceptions\TimeoutException when the wait runs out — the job
// keeps running server-side and can still be polled afterwards)
$done = $client->smartEnrichment()->waitUntilComplete(
$bulk->jobId,
timeoutSeconds: 120.0,
pollIntervalSeconds: 2.0,
);
// Lookup history (paginated, newest first; optional free-text search over the
// queried company name and the resolved VAT number / matched name; perPage is
// capped at 100 server-side)
$history = $client->smartEnrichment()->history(page: 1, perPage: 25, search: 'Example');
echo $history->total . ' lookups' . PHP_EOL;
echo ($history->stats?->found ?? 0) . ' of ' . ($history->stats?->total ?? 0) . ' ever matched' . PHP_EOL;
foreach ($history as $row) {
echo $row->queryCompanyName . ' → ' . ($row->result?->vatNumber ?? $row->status->value) . PHP_EOL;
}
// Quota usage for the current billing period (+ recent monthly billing history)
$usage = $client->smartEnrichment()->usage();
echo $usage->used . '/' . $usage->
// Prefill company data from the French SIRENE registry by SIRET, SIREN or FR VAT
// number (rate limit: 3 requests/minute).
$prefill = $client->eReporting()->sireneLookup('12345678900012');
$enrollment = $client->eReporting()->createEnrollment(
email: '[email protected]', // notification e-mail (ret: $prefill->siret, // 14 chars — at least one of siret/siren
siren: $prefill->siren, // 9-char fallback when no head office SIRET exists
companyName: $prefill->companyName,
address: $prefill->address,
city: $prefill->city,
postalcode: $prefill->postalcode,
autoActivate: true, // false = create the account only
);
echo $enrollment->status->value; // e.g. regime_activated
echo $enrollment->statusLabel; // human-readable label from the API
// Later: list / fetch enrollments (paginated, perPage capped at 100)
$enrollments = $client->eReporting()->listEnrollments(page: 1, perPage: 25);
$enrollment = $client->eReporting()->getEnrollment($enrollment->id);
use Taxora\Sdk\Enums\ComplianceTransactionType;
use Taxora\Sdk\ValueObjects\ComplianceInvoiceLine;
use Taxora\Sdk\ValueObjects\ComplianceLineTax;
$tx = $client->eReporting()->createTransaction(
enrollmentId: $enrollment->id,
transactionType: ComplianceTransactionType::B2C_OUTBOUND, // or 'b2c_outbound'
invoiceNumber: 'TKT-2026-00001', // max 50 chars
invoiceDate: '2026-06-15', // Y-m-d or DateTimeInterface
subtotal: '100.00',
total: '120.00',
invoiceLines: [
new ComplianceInvoiceLine(
description: 'Museum ticket',
quantity: 2,
price: 50,
taxes: [new ComplianceLineTax(name: 'TVA', percent: 20, category: 'S')],
),
],
currency: 'EUR', // optional, defaults to EUR
taxAmount: '20.00',
submitNow: false, // defer provider submission
);
// Creation is idempotent: retrying the same invoice (enrollment + type + number +
// date + counterparty VAT) returns the existing transaction instead of a duplicate.
// Pending transactions can still be edited (PATCH semantics — only what you pass
// is changed; nullable fields cannot be cleared back to null through the SDK)
// or deleted; submit (or retry) explicitly when ready. Note: per-invoice
// submission
// Semicolon-separated CSV, max 2 MB; rows are grouped into invoices by
// invoice_number, re-uploads are idempotent. Required columns: invoice_number,
// invoice_date, transaction_type, subtotal, total, line_description,
// line_quantity, line_price, tax_name, tax_percent, tax_category.
$result = $client->eReporting()->importTransactions(
$enrollment->id,
file_get_contents('transactions.csv'), // raw CSV content
'transactions.csv',
);
echo $result->created . ' created, ' . $result->skippedDuplicates . ' duplicates' . PHP_EOL;
foreach ($result->errors as $error) {
echo $error . PHP_EOL; // per-row problems, e.g. bad tax category
}
$reports = $client->eReporting()->listTaxReports(state: 'refused'); // new | sent | acknowledged | registered | refused | error
foreach ($reports as $report) {
echo $report->state->value . ' — ' . ($report->refusalReason ?? 'ok') . PHP_EOL;
}
$report = $client->eReporting()->getTaxReport(5);
if ($report->state->isFailure()) {
echo $report->refusalReason; // e.g. CDV 301 details
}
$stats = $client->eReporting()->getRevenueStatistics(
dateFrom: '2026-01-01', // optional — defaults to 12 months ago (server-side)
dateTo: new DateTimeImmutable(), // optional — defaults to today
interval: 'month', // 'day' | 'week' | 'month' (default 'month')
transactionType: 'b2c_outbound', // optional filter (string or ComplianceTransactionType)
state: 'submitted', // optional filter (string or ComplianceTransactionState)
);
echo $stats->primaryCurrency; // e.g. EUR
echo $stats->totals->total; // e.g. 120000.0000
foreach ($stats->timeSeries as $bucket) {
echo $bucket->bucket . ': ' . $bucket->total . PHP_EOL; // 2026-01: 10800.0000
}
foreach ($stats->byCountry as $row) {
echo $row['country'] . ' → ' . $row['total'] . PHP_EOL; // 'unknown' groups NULL partners
}
// Canonical VAT rates & DGFiP tax categories for a reporting country — the valid
// `category` values for ComplianceLineTax (an `E` rate needs a VATEX comment).
$rates = $client->eReporting()->getVatRates('FR');
foreach ($rates->rates as $rate) {
echo $rate->percent . '% (' . $rate->category . ')' . ($rate->
// The one compliance endpoint that works WITHOUT the e-reporting feature —
// it files an activation request with the Taxora team. Identity defaults to
// the authenticated user; all parameters are optional context.
$client->eReporting()->requestEReportingAccess(
company: 'Acme SARL',
phone: '+33 1 23 45 67 89',
message: 'We need Flux 10 e-reporting starting September.',
language: 'fr',
service: 'e_reporting', // optional: e_reporting (default) | e_invoicing
countries: ['FR'], // optional: ISO alpha-2 codes you need
);
// Brønnøysund register lookup by org number (spaces / "NO…MVA" accepted → one
// result) or by name (search). 404 = nothing found; rate limit 30/minute.
$companies = $client->eReporting()->registryLookup('NO', '923 609 016');
$company = $companies[0]; // RegistryCompany
$enrollment = $client->eReporting()->createNorwayEnrollment(
email: '[email protected]', // notification e-mail
orgNumber: $company->orgNumber, // also the Peppol id (scheme 0192)
companyName: $company->companyName ?? 'Example AS',
address: $company->address ?? 'Storgata 1',
city: $company->city ?? 'Oslo',
postalcode: $company->postalcode ?? '0155',
vatRegistered: $company->vatRegistered, // MVA-registered (default true)
enterpriseRegister: $company->enterpriseRegister, // adds "Foretaksregisteret" to invoices
reception: false, // true = also receive e-invoices over Peppol
);
echo $enrollment->service?->value; // e_invoicing
echo $enrollment->regime; // peppol_bis3
use Taxora\Sdk\Enums\PeppolLookupStatus;
$peppol = $client->eReporting()->peppolLookup('NO', '974760673'); // scheme defaults to 0192
if ($peppol->status === PeppolLookupStatus::PENDING) {
sleep(3); // the directory resolves asynchronously — ask again
$peppol = $client->eReporting()->peppolLookup('NO', '974760673');
}
if ($peppol->status->isSuccess()) { // reachable: buyer is in ELMA → send EHF
print_r($peppol->documentTypes); // e.g. xml.ubl.invoice.bis3, xml.ubl.credit_note.bis3
}
use Taxora\Sdk\ValueObjects\ComplianceInvoiceLine;
use Taxora\Sdk\ValueObjects\ComplianceLineTax;
$tx = $client->eReporting()->createTransaction(
enrollmentId: $enrollment->id,
transactionType: 'b2b_domestic_outbound', // the only type for Norway
invoiceNumber: 'NO-2026-0001',
invoiceDate: '2026-10-01',
dueDate: '2026-10-31',
currency: 'NOK',
subtotal: '1000.00',
taxAmount: '250.00',
total: '1250.00',
invoiceLines: [
new ComplianceInvoiceLine(
description: 'Consulting',
quantity: 10,
price: 100,
// Norwegian rates: getVatRates('NO') — 25/15/12 % are category S; AE = reverse charge
taxes: [new ComplianceLineTax(name: 'MVA', percent: 25, category: 'S')],
),
],
counterpartyName: 'Buyer AS', //
use GuzzleHttp\Client as GuzzleAdapter;
use Http\Factory\Guzzle\RequestFactory;
use Http\Factory\Guzzle\StreamFactory;
use Taxora\Sdk\TaxoraClientFactory;
$client = TaxoraClientFactory::create(
apiKey: 'YOUR_X_API_KEY',
http: new GuzzleAdapter(),
requestFactory: new RequestFactory(),
streamFactory: new StreamFactory()
);
$client->auth()->login('email', 'password', device: 'my-server-01');
// Passing device is optional; omitted value falls back to a generated host-based identifier.
use Symfony\Component\Cache\Adapter\FilesystemAdapter;
use Symfony\Component\Cache\Psr16Cache;
use Taxora\Sdk\Http\Psr16TokenStorage;
$cache = new Psr16Cache(new FilesystemAdapter());
$storage = new Psr16TokenStorage($cache);
$client = new TaxoraClient($http, $reqF, $strF, 'YOUR_KEY', $storage);
$client = new TaxoraClient(..., environment: Environment::PRODUCTION);
use Taxora\Sdk\Http\RetryPolicy;
$client = TaxoraClientFactory::create(
apiKey: 'YOUR_X_API_KEY',
retryPolicy: new RetryPolicy(
maxAttempts: 5,
initialDelayMs: 250,
retryableStatusCodes: [502, 503, 504], // add or drop status codes
respectRetryAfter: true, // false = ignore the header entirely
maxRetryAfterMs: 10_000, // longest wait accepted from Retry-After
retryOnNetworkErrors: true, // false = surface transport errors at once
),
);
// or, for callers that do their own retrying (queue workers, cron jobs):
$client = TaxoraClientFactory::create(apiKey: '…', retryPolicy: RetryPolicy::disabled());
bash
composer
Loading please wait ...
Before you can download the PHP files, the dependencies should be resolved. This can take some minutes. Please be patient.