Skip to main content
Transaction turns a merchant name or card descriptor into a merchant: a display name, a confidence score, and a domain and brand id when it identifies one. Every non-empty input returns a row.
Transaction is a private beta. Contact sales to turn it on, then open the Transaction Playground.
Card descriptor matching is experimental. Test it with representative inputs before you depend on the results. A descriptor that contains several businesses can be ambiguous. The API returns no industry or spending category. Identify a merchant is the reference for every field, value, and status code. This page explains how to use them.

Get your beta key

Access belongs to your Logo.dev account. When the beta is on, everyone on the account can use it. Each person has their own key. Copy yours from the Transaction Playground, and keep it on your server. The key starts with tx_beta_ and works only with Transaction. Your Logo.dev secret keys do not work here. To replace your key, select Regenerate in the playground. The old key stops working immediately. Your daily usage does not reset.

Identify a merchant

Send a POST request to https://www.logo.dev/api/transaction with the descriptor in transaction. country_code is optional. It is a hint for ambiguous names, not a filter.
The response looks like this. The values are illustrative.
logo_url has no token. Add your publishable key before you show the logo: https://img.logo.dev/starbucks.com?token=LOGO_DEV_PUBLISHABLE_KEY.

Read the row

Every row has a status. Handle the two values differently.
  • matched: the API identified a merchant domain. Show name and the logo.
  • cleaned: the API tidied the descriptor into a display name and identified no merchant. domain, entity_id, and logo_url are null. Show name without a logo.
A cleaned row is an answer, not an error. Code that checks status === "matched" covers every case, because the API returns no third status.
Three more fields tell you how far to trust the row and how to store it:
  • Key your records on entity_id. It is the merchant’s Logo.dev brand id, the same id Brand uses. A brand keeps its id when its domain changes, so a rebrand stays one merchant.
  • Branch on entity_id, not on confidence. A row with an id has two sources that agree: the pipeline identified the merchant, and Brand resolves a brand for the same domain. A matched row can still have a null id, for example when Logo.dev has not indexed the domain yet.
  • Rank on confidence. Use the score to sort a review queue or to hold the bottom of a batch for a person. Logo.dev publishes no cut-off and can recalibrate the score.
match_type names the pipeline stage that produced the row. Read it from the row and do not predict it from the descriptor. The reference lists each value.

Group by owner and by platform

Two fields name companies that sit beside the merchant. Neither one replaces the merchant in domain.
  • parent is the company that owns the merchant. A lookup for Facebook returns Facebook, with Meta in parent. Group totals on parent.entity_id to add up one company’s brands. Coverage is thin, so most rows return null. A null does not mean the merchant is independent.
  • intermediary is the platform the purchase ran through. A coffee paid through Square returns the coffee shop, with Square in intermediary. Group totals on intermediary.entity_id to add up spend by platform.
Both objects have the same four fields as the merchant: domain, name, entity_id, and logo_url.

Add transaction context (optional)

Send transaction_context in these cases:
  • The account holder matters. Without context, the API does not detect people, so Jane Doe can match an organization with a similar name.
  • You need the platform named reliably. Without context, the API fills intermediary only for some prefixed descriptors, such as SQ*.
Pass the real transaction values. The API fills in no placeholder values.
When you send the object, date, amount, currency, and entry_type are required. account_holder is optional. With context, the response adds entity_type: person, organization, or null. A person always comes back as a cleaned row. The merchant string, the context, and the account holder details go to Logo.dev’s enrichment provider. The beta keeps no account history across lookups.

Daily allowance

Each person gets 1,000 requests per UTC day, shared between the playground and their beta key. The allowance resets at midnight UTC. Beta requests do not spend API credits. Every request that passes validation counts. This includes cleaned rows, provider errors, timeouts, and repeats served from a saved result. Invalid input and rejected keys do not count. A burst limit also applies: more than about 60 requests in one minute returns 429. Each response that counts carries X-RateLimit-Limit, X-RateLimit-Remaining, and X-RateLimit-Reset.

Handle errors

Transaction does not use the Logo.dev API error object. An error has one error string, for example {"error":"Daily limit of beta requests reached"}. The reference lists every status code with an example.
  • 400: fix the request body. It does not count against your allowance.
  • 401: check the key. A regenerated key replaces the old one.
  • 403: the beta is not on for your account. Contact sales.
  • 409: another request is already looking up the same transaction. Wait for the number of seconds in Retry-After, then retry to read the saved result.
  • 429: wait for the number of seconds in Retry-After.
  • 503: a dependency is unavailable. Retry later. The API returns this error instead of a cleaned row.

Changes for existing callers

The response shape changed during the beta:
  • status no longer returns not_found. Those rows now return cleaned. Code that checks for not_found matches nothing, so any fallback behind it stops running.
  • brand_url and verified are gone. verified was true exactly when entity_id is set. Test entity_id instead.
  • New fields. name, confidence, match_type, logo_url, entity_id, parent, and intermediary are on every response. A parser that rejects unknown fields needs an update.
  • New match_type values. descriptor_domain and searched_name appear on rows that returned cleaned_descriptor before. Add them before you upgrade.