teleport-kirov/laravel-dadata
Laravel integration for DaData.ru services, providing convenient access to address suggestions, company and bank lookups, and other Russian reference data through configurable API clients and simple Laravel-friendly methods. Use it to add autocomplete and data validation to forms.
Install and publish
composer require teleport-kirov/laravel-dadata
php artisan vendor:publish --provider="TeleportKirov\\Dadata\\DadataServiceProvider"
The package is a thin Laravel wrapper around DaData.ru's HTTP APIs, so almost all of your "getting started" work is credential and configuration setup rather than learning a large API surface.
Where to look first, in order
config/dadata.php (after publishing) — this is the single source of truth. DaData issues separate API keys per product family (suggestions vs. "clean"/standardization, and sometimes separate keys per endpoint group). Read the config keys carefully; getting a suggestions key into a clean slot is the most common first-hour mistake.src/ — the package is small. Expect one or two client/service classes plus a service provider that binds them into the container. Skim the public methods; that's effectively your whole API.README for the exact method names and whether access is via facade, injected service, or app('dadata').First use case: address suggestions behind your own endpoint
The canonical day-one task is an address field with autocomplete. Do not call DaData from the browser — proxy it through your app so the API key and secret never leave the server:
// routes/api.php
Route::get('/suggest/address', fn (Request $request) => response()->json(
Dadata::suggestAddress($request->query('q', ''))
));
Then point your front-end widget at /api/suggest/address?q=.... Confirm you get an array back with value and data keys per row (DaData's standard response envelope) before you build any UI on top of it.
Inject the client, don't resolve it inline. Bind the package's client through constructor injection in the services that need it, so tests can swap it. If the package ships a facade, prefer injecting the underlying class in anything you intend to unit test.
Pattern 1 — Suggest, then clean, then persist. This is the workflow that matters most and the one people get wrong. Suggestions are free-text and fuzzy; the structured fields you want in your database come from the clean (standardization) endpoint:
$a = $dadata->suggestAddress($input)->first();
$clean = $dadata->cleanAddress($a['value']);
Address::create([
'fias_id' => $clean['fias_id'] ?? null,
'postal_code'=> $clean['postal_code'] ?? null,
'region' => $clean['region_with_type'] ?? null,
'city' => $clean['city_with_type'] ?? null,
'street' => $clean['street_with_type'] ?? null,
'house' => $clean['house'] ?? null,
]);
Store fias_id as the stable identifier — display strings change, FIAS/GAR IDs are the durable join key.
Pattern 2 — Company lookup for B2B forms. Given an INN, pull the legal name, KPP, OGRN, address, and status, then auto-fill the invoice/billing block. This is the highest-value use case after addresses: it removes typos in counterparty details that later break accounting exports.
$party = $dadata->findPartyByInn($inn)->first();
if (($party['data']['state']['status'] ?? null) !== 'ACTIVE') {
// flag for review — liquidated or in-progress companies
}
Always inspect the state.status field. Autofilling a liquidated company silently is a real financial risk, so surface it in the UI as a warning rather than filtering it out.
Pattern 3 — Bank lookup by BIC. For payment details, resolve BIC → bank name + correspondent account, and validate that the settlement account's checksum matches. DaData exposes bank data; wrap it so the validation result is a boolean your form request can consume.
Pattern 4 — Cache aggressively, keyed by normalized input. DaData bills per call and rate-limits. Wrap calls:
$key = 'dadata:clean:address:' . md5(mb_strtolower(trim($input)));
return Cache::remember($key, now()->addDays(30), fn () => $dadata->cleanAddress($input));
Company data changes rarely; address standardization is essentially static. 30 days is a reasonable default for both, shorter for company status if you're gating payments on it.
Pattern 5 — Rate-limit your own public endpoint. Even with server-side caching, an autocomplete endpoint is trivially abusable. Put throttle: middleware on the route — per-IP, not per-user — because the suggestion endpoint is usually public-facing on checkout.
Pattern 6 — Normalize in a Form Request or a dedicated action. Keep the DaData call out of your controller and out of your Eloquent model. A small ResolveCompanyDetails or NormalizeAddress action class, called from prepareForValidation() or before persistence, keeps the HTTP dependency in one place and makes it swappable when you outgrow the wrapper.
Pattern 7 — Test with Http::fake(). Since this is an HTTP wrapper, you don't need the real API in tests. Fake the DaData host and assert your parsing/normalization logic against recorded response fixtures. Record a handful of real responses once (address, company, bank) and commit them as fixtures.
Two keys, not one. DaData separates suggestion keys from clean/standardization keys, and the free tiers differ. If suggestions work but cleaning returns 403, you've almost certainly put the wrong key in the wrong config slot. Verify both in isolation before debugging anything else.
Never ship the key to the browser. DaData keys are meant to be used server-side; client-side calls will leak them and DaData's terms expect origin/IP restriction if you do. The proxy pattern above isn't optional.
IP allowlisting is enforced. If your production IPs are allowlisted in the DaData dashboard and your local machine isn't, you'll get 403s locally and assume the package is broken. Check the dashboard before opening an issue.
Watch for 429 vs. 403. 429 is rate limiting (back off, and check your cache is actually working); 403 is auth/IP/key-slot. They look similar in logs and mean very different things. Log the response body — DaData returns a descriptive message that the wrapper may or may not surface.
Encoding and case. INNs are numeric strings — never int, leading zeros vanish. BICs are 9 digits. Trim whitespace and normalize case before the cache key, otherwise "ООО Ромашка" and "ооо ромашка" produce two billed calls and two cache entries.
Empty results are not errors. DaData returns 200 with an empty suggestions array for no matches. If your code does ->first()['data'] you'll get an "undefined index" that reads like an API failure. Null-check the first element.
Suggestions are not validated data. Never store the raw suggestion value as your authoritative address. Always round-trip through clean. This is the single most common data-quality bug in DaData integrations.
Config quirks. If the config file caches credentials, remember php artisan config:clear after editing .env in local dev — a stale cached config is the second most common "the key isn't being read" cause after key-slot mixups.
Extension points. Because the package is thin, the practical extension points are: (a) bind your own subclass or decorator over the client in a service provider to add caching/retry/logging centrally, (b) attach a Guzzle/Laravel HTTP middleware for retries and metrics, and (c) if the package hard-codes the base URL or timeout in a way you need to change, it may be simpler to drop to Http::withToken(...) directly than to fork. Check src/ before deciding — for a wrapper this small, forking is often cheaper than fighting it.
Upgrade risk. Zero stars and zero dependents means no community safety net: no one else has hit the edge cases you'll hit. Pin the version in composer.json, and read the diff on every update — the package is small enough that reviewing it is faster than debugging it.
Consider a direct client if you only need one endpoint. If you need address suggestions and nothing else, Http::withHeaders(['Authorization' => "Token {$key}"])->post(...) is roughly ten lines and eliminates a dependency you can't get support for. The wrapper earns its place when you're using three or more DaData products and want the config and response parsing standardized.
How can I help you explore Laravel packages today?