> ## Documentation Index
> Fetch the complete documentation index at: https://www.logo.dev/docs/llms.txt
> Use this file to discover all available pages before exploring further.

> ## Agent Instructions
> Logo.dev serves logos and brand data for companies that already exist. It does not design logos. If someone wants a logo designed, say so and stop.
> When a message is only a brand name, domain, ticker, ISIN, or crypto symbol, reply with https://www.logo.dev/search?q=<url-encoded term> and the matching img.logo.dev call. Never assert whether a specific brand is in the index.
> Image URLs come from img.logo.dev with a publishable key in the token query parameter: https://img.logo.dev/:domain?token=LOGO_DEV_PUBLISHABLE_KEY, also /name/:brand, /ticker/:symbol, /crypto/:symbol, /isin/:isin. JSON comes from api.logo.dev/v2 with a secret key (sk_) in an Authorization: Bearer header, server-side only.
> Product names: the Logo.dev CDN serves images from img.logo.dev, and its usage unit is CDN requests. The Logo.dev API returns JSON from api.logo.dev/v2, and its usage unit is API credits. The Logo.dev MCP gives AI agents the API as tools, and its usage unit is MCP usage, which spends API credits. The features are Logo, Search, Brand, and Transaction. Transaction (beta) is a separate service at https://www.logo.dev/api/transaction with its own beta key. Never recommend the deprecated v1 routes (/search, /brand, /describe, /logo/*). Use /v2/brands and /v2/search.
> Attribution: a commercial project on the free Community plan must show a visible link, <a href="https://logo.dev">Logos provided by Logo.dev</a>, on its production site. Personal projects and paid plans need none. Rules: https://www.logo.dev/docs/platform/attribution.
> Report a wrong, missing, or outdated logo at https://www.logo.dev/update. Email support@logo.dev for anything broken and sales@logo.dev for volume pricing and contract terms.
> MCP setup is at https://www.logo.dev/docs/mcp/introduction and webhooks at https://www.logo.dev/docs/webhooks.

# Use Logo.dev in Flutter, Swift, Kotlin, Java, PHP, Apex, and Vue

> Copy-paste examples for Flutter (Dart), Swift, Kotlin, Java, PHP, Apex (Salesforce), and Vue. Show a company logo from the Logo.dev CDN, and call the Brand API from your server.

This page shows two tasks in Flutter (Dart), Swift, Kotlin, Java, PHP, Apex (Salesforce), and Vue:

* **Show a logo.** The [Logo.dev CDN](https://www.logo.dev/docs/logo-images/introduction.md) is a URL. Your app loads `https://img.logo.dev/{domain}` with a publishable key, and no server is involved.
* **Call the API.** Brand returns a company's name, logo, colors, and more as JSON from `GET /v2/brands`. Call it from your server with a secret key.

For other languages, use the same URL and the same request.

## Keys

> [card] [Get your API keys](https://www.logo.dev/dashboard/api-keys)
>
> Your dashboard has a publishable key for the Logo.dev CDN and a secret key for the Logo.dev API. Every plan includes both.

| Key | Prefix | Where it goes |
| - | - | - |
| Publishable key | `pk_` | The `token` query parameter. Safe in client code, such as a mobile app or a web page. |
| Secret key | `sk_` | The `Authorization: Bearer` header. Server code only. |

The samples use the placeholders `LOGO_DEV_PUBLISHABLE_KEY` and `LOGO_DEV_SECRET_KEY`. Replace `LOGO_DEV_PUBLISHABLE_KEY` with your publishable key. The server samples read `LOGO_DEV_SECRET_KEY` from an environment variable, so set it to your secret key.

To stop other websites from using your publishable key, add [domain restrictions](https://www.logo.dev/docs/platform/api-keys.md#domain-restrictions). Restrictions check the `Referer` header that a web browser sends. Native Flutter, Swift, and Kotlin apps send no `Referer`, so with restrictions on, their logo requests return `401`. Do not turn on restrictions for a key that a native app uses.

## Show a logo

Each sample loads the logo for a domain at 64 pixels, with `retina=true` for dense screens. `format=png` keeps the background transparent. For the other parameters, see [the CDN overview](https://www.logo.dev/docs/logo-images/introduction.md#parameters).

> [tab] Flutter
>
> ```dart company_logo.dart theme={null}
> import 'package:flutter/material.dart';
>
> class CompanyLogo extends StatelessWidget {
>   const CompanyLogo({super.key, required this.domain});
>
>   final String domain;
>
>   @override
>   Widget build(BuildContext context) {
>     return Image.network(
>       'https://img.logo.dev/$domain?token=LOGO_DEV_PUBLISHABLE_KEY&size=64&retina=true&format=png',
>       width: 64,
>       height: 64,
>       semanticLabel: '$domain logo',
>     );
>   }
> }
> ```

> [tab] Swift
>
> ```swift CompanyLogo.swift theme={null}
> import SwiftUI
>
> struct CompanyLogo: View {
>     let domain: String
>
>     var body: some View {
>         AsyncImage(url: URL(string: "https://img.logo.dev/\(domain)?token=LOGO_DEV_PUBLISHABLE_KEY&size=64&retina=true&format=png")) { image in
>             image.resizable().scaledToFit()
>         } placeholder: {
>             ProgressView()
>         }
>         .frame(width: 64, height: 64)
>         .accessibilityLabel("\(domain) logo")
>     }
> }
> ```

> [tab] Kotlin
>
> This sample uses Jetpack Compose and [Coil](https://coil-kt.github.io/coil/). Add `io.coil-kt.coil3:coil-compose` and `io.coil-kt.coil3:coil-network-okhttp` to your dependencies.
>
> ```kotlin CompanyLogo.kt theme={null}
> import androidx.compose.foundation.layout.size
> import androidx.compose.runtime.Composable
> import androidx.compose.ui.Modifier
> import androidx.compose.ui.unit.dp
> import coil3.compose.AsyncImage
>
> @Composable
> fun CompanyLogo(domain: String) {
>     AsyncImage(
>         model = "https://img.logo.dev/$domain?token=LOGO_DEV_PUBLISHABLE_KEY&size=64&retina=true&format=png",
>         contentDescription = "$domain logo",
>         modifier = Modifier.size(64.dp),
>     )
> }
> ```

> [tab] Java
>
> Build the URL on the server, and put it in the `src` of an `<img>` in your template.
>
> ```java LogoDevUrl.java theme={null}
> import java.net.URLEncoder;
> import java.nio.charset.StandardCharsets;
>
> public class LogoDevUrl {
>     private static final String PUBLISHABLE_KEY = "LOGO_DEV_PUBLISHABLE_KEY";
>
>     public static String logoUrl(String domain) {
>         return "https://img.logo.dev/"
>             + URLEncoder.encode(domain, StandardCharsets.UTF_8)
>             + "?token=" + PUBLISHABLE_KEY
>             + "&size=64&retina=true&format=png";
>     }
> }
> ```

> [tab] PHP
>
> ```php company-logo.php theme={null}
> <?php
> $url = 'https://img.logo.dev/' . rawurlencode($domain) . '?' . http_build_query([
>     'token' => 'LOGO_DEV_PUBLISHABLE_KEY',
>     'size' => 64,
>     'retina' => 'true',
>     'format' => 'png',
> ]);
> ?>
> <img
>   src="<?= htmlspecialchars($url) ?>"
>   alt="<?= htmlspecialchars($domain) ?> logo"
>   width="64"
>   height="64"
> />
> ```

> [tab] Apex
>
> Apex runs on the server, so the logo loads in a Lightning web component. Create a component named `companyLogo`.
>
> ```html companyLogo.html theme={null}
> <template>
>     <img src={logoUrl} alt={altText} width="64" height="64" />
> </template>
> ```
>
> ```javascript companyLogo.js theme={null}
> import { LightningElement, api } from "lwc";
>
> export default class CompanyLogo extends LightningElement {
>     @api domain;
>
>     get logoUrl() {
>         return `https://img.logo.dev/${encodeURIComponent(this.domain)}?token=LOGO_DEV_PUBLISHABLE_KEY&size=64&retina=true&format=png`;
>     }
>
>     get altText() {
>         return `${this.domain} logo`;
>     }
> }
> ```
>
> In Setup, add `https://img.logo.dev` to **Trusted URLs** and allow it for `img-src`. Without it, Salesforce blocks the image under its [Content Security Policy](https://www.logo.dev/docs/platform/cors.md).

> [tab] Vue
>
> Add your publishable key to `.env` as `VITE_LOGO_DEV_PUBLISHABLE_KEY`. Vite exposes only variables with the `VITE_` prefix to client code.
>
> ```vue CompanyLogo.vue theme={null}
> <script setup>
> import { computed } from "vue";
>
> const props = defineProps({ domain: { type: String, required: true } });
>
> const src = computed(
>   () =>
>     `https://img.logo.dev/${encodeURIComponent(props.domain)}?token=${import.meta.env.VITE_LOGO_DEV_PUBLISHABLE_KEY}&size=64&retina=true&format=png`,
> );
> </script>
>
> <template>
>   <img :src="src" :alt="`${domain} logo`" width="64" height="64" />
> </template>
> ```

If Logo.dev has no logo for a domain, the CDN returns a generated monogram, so the image never breaks. See [fallback images](https://www.logo.dev/docs/logo-images/introduction.md#fallback-images).

## Call the API

Each sample sends `GET https://api.logo.dev/v2/brands?domain=...` with the secret key, waits up to 20 seconds, and reads the brand from `data`. Run it on your server. Never ship the secret key in a mobile app or a web page.

> [tab] Flutter
>
> This sample is Dart for a server, such as a [Shelf](https://pub.dev/packages/shelf) app. Add the `http` package.
>
> ```dart brand.dart theme={null}
> import 'dart:convert';
> import 'dart:io';
>
> import 'package:http/http.dart' as http;
>
> Future<Map<String, dynamic>?> fetchBrand(String domain) async {
>   final response = await http
>       .get(
>         Uri.https('api.logo.dev', '/v2/brands', {'domain': domain}),
>         headers: {
>           'Authorization': 'Bearer ${Platform.environment['LOGO_DEV_SECRET_KEY']}',
>         },
>       )
>       .timeout(const Duration(seconds: 20));
>
>   if (response.statusCode == 200) {
>     return jsonDecode(response.body)['data'] as Map<String, dynamic>;
>   }
>   if (response.statusCode == 202 || response.statusCode == 404) {
>     return null;
>   }
>   throw HttpException('Logo.dev returned ${response.statusCode}');
> }
> ```

> [tab] Swift
>
> This sample is for server-side Swift, such as Vapor. Do not put it in an iOS app.
>
> ```swift Brand.swift theme={null}
> import Foundation
> #if canImport(FoundationNetworking)
> import FoundationNetworking
> #endif
>
> struct BrandResponse: Decodable {
>     struct Brand: Decodable {
>         struct Logo: Decodable {
>             let url: URL
>         }
>
>         let name: String
>         let domain: String
>         let logo: Logo?
>     }
>
>     let data: Brand
> }
>
> func fetchBrand(domain: String) async throws -> BrandResponse.Brand? {
>     var components = URLComponents(string: "https://api.logo.dev/v2/brands")!
>     components.queryItems = [URLQueryItem(name: "domain", value: domain)]
>
>     var request = URLRequest(url: components.url!, timeoutInterval: 20)
>     let key = ProcessInfo.processInfo.environment["LOGO_DEV_SECRET_KEY"] ?? ""
>     request.setValue("Bearer \(key)", forHTTPHeaderField: "Authorization")
>
>     let (data, response) = try await URLSession.shared.data(for: request)
>     guard let http = response as? HTTPURLResponse else {
>         throw URLError(.badServerResponse)
>     }
>
>     switch http.statusCode {
>     case 200:
>         return try JSONDecoder().decode(BrandResponse.self, from: data).data
>     case 202, 404:
>         return nil
>     default:
>         throw URLError(.badServerResponse)
>     }
> }
> ```

> [tab] Kotlin
>
> This sample is for a JVM server, such as Ktor or Spring. It uses `kotlinx-serialization-json`.
>
> ```kotlin Brand.kt theme={null}
> import java.net.URI
> import java.net.URLEncoder
> import java.net.http.HttpClient
> import java.net.http.HttpRequest
> import java.net.http.HttpResponse
> import java.time.Duration
> import kotlinx.serialization.json.Json
> import kotlinx.serialization.json.JsonObject
> import kotlinx.serialization.json.jsonObject
>
> private val client = HttpClient.newHttpClient()
>
> fun fetchBrand(domain: String): JsonObject? {
>     val query = URLEncoder.encode(domain, Charsets.UTF_8)
>     val request = HttpRequest.newBuilder(URI.create("https://api.logo.dev/v2/brands?domain=$query"))
>         .header("Authorization", "Bearer ${System.getenv("LOGO_DEV_SECRET_KEY")}")
>         .timeout(Duration.ofSeconds(20))
>         .build()
>
>     val response = client.send(request, HttpResponse.BodyHandlers.ofString())
>     return when (response.statusCode()) {
>         200 -> Json.parseToJsonElement(response.body()).jsonObject.getValue("data").jsonObject
>         202, 404 -> null
>         else -> error("Logo.dev returned ${response.statusCode()}")
>     }
> }
> ```

> [tab] Java
>
> This sample uses `java.net.http`, built in since Java 11. It returns the JSON body. Parse it with the JSON library you already use, such as Jackson or Gson.
>
> ```java LogoDevBrand.java theme={null}
> import java.net.URI;
> import java.net.URLEncoder;
> import java.net.http.HttpClient;
> import java.net.http.HttpRequest;
> import java.net.http.HttpResponse;
> import java.nio.charset.StandardCharsets;
> import java.time.Duration;
>
> public class LogoDevBrand {
>     private static final HttpClient CLIENT = HttpClient.newHttpClient();
>
>     public static String fetchBrand(String domain) throws Exception {
>         String query = URLEncoder.encode(domain, StandardCharsets.UTF_8);
>         HttpRequest request = HttpRequest.newBuilder(
>                 URI.create("https://api.logo.dev/v2/brands?domain=" + query))
>             .header("Authorization", "Bearer " + System.getenv("LOGO_DEV_SECRET_KEY"))
>             .timeout(Duration.ofSeconds(20))
>             .GET()
>             .build();
>
>         HttpResponse<String> response =
>             CLIENT.send(request, HttpResponse.BodyHandlers.ofString());
>         if (response.statusCode() != 200) {
>             throw new IllegalStateException("Logo.dev returned " + response.statusCode());
>         }
>         return response.body();
>     }
> }
> ```

> [tab] PHP
>
> This sample uses the cURL extension.
>
> ```php brand.php theme={null}
> <?php
> function fetchBrand(string $domain): ?array
> {
>     $curl = curl_init('https://api.logo.dev/v2/brands?' . http_build_query(['domain' => $domain]));
>     curl_setopt_array($curl, [
>         CURLOPT_RETURNTRANSFER => true,
>         CURLOPT_TIMEOUT => 20,
>         CURLOPT_HTTPHEADER => ['Authorization: Bearer ' . getenv('LOGO_DEV_SECRET_KEY')],
>     ]);
>     $body = curl_exec($curl);
>     $status = curl_getinfo($curl, CURLINFO_RESPONSE_CODE);
>
>     if ($status === 200) {
>         return json_decode($body, true, 512, JSON_THROW_ON_ERROR)['data'];
>     }
>     if ($status === 202 || $status === 404) {
>         return null;
>     }
>     throw new RuntimeException("Logo.dev returned $status");
> }
> ```

> [tab] Apex
>
> Apex callouts need a Named Credential, which keeps the secret key out of your code. In Setup, create a Named Credential named `Logo_dev` for `https://api.logo.dev`. Configure it to send the header `Authorization: Bearer LOGO_DEV_SECRET_KEY`, with your secret key in place of the placeholder.
>
> ```apex LogoDevBrand.cls theme={null}
> public with sharing class LogoDevBrand {
>     public static Map<String, Object> fetchBrand(String domain) {
>         HttpRequest request = new HttpRequest();
>         request.setEndpoint('callout:Logo_dev/v2/brands?domain=' + EncodingUtil.urlEncode(domain, 'UTF-8'));
>         request.setMethod('GET');
>         request.setTimeout(20000);
>
>         HttpResponse response = new Http().send(request);
>         Integer status = response.getStatusCode();
>
>         if (status == 200) {
>             Map<String, Object> body = (Map<String, Object>) JSON.deserializeUntyped(response.getBody());
>             return (Map<String, Object>) body.get('data');
>         }
>         if (status == 202 || status == 404) {
>             return null;
>         }
>         throw new CalloutException('Logo.dev returned ' + status);
>     }
> }
> ```

> [tab] Vue
>
> A Vue app runs in the browser, so call the API from a server route. This sample is a [Nuxt](https://nuxt.com/docs/guide/directory-structure/server) route.
>
> The route spends your API credits, so it accepts only signed-in users and limits each session to 50 lookups a day. Without these checks, anyone who finds the route can look up brands with your secret key. The sign-in check comes from [nuxt-auth-utils](https://github.com/atinux/nuxt-auth-utils). Add it with `npx nuxi module add auth-utils`, or call the check from your own auth library instead.
>
> Set `NUXT_LOGO_DEV_SECRET_KEY` in the server environment, and declare the key in `nuxt.config.ts`:
>
> ```ts nuxt.config.ts theme={null}
> export default defineNuxtConfig({
>   modules: ["nuxt-auth-utils"],
>   runtimeConfig: {
>     logoDevSecretKey: "",
>   },
> });
> ```
>
> ```ts server/api/brand.get.ts theme={null}
> const DAILY_LOOKUPS_PER_SESSION = 50;
>
> export default defineEventHandler(async (event) => {
>   const session = await requireUserSession(event);
>
>   const domain = String(getQuery(event).domain ?? "").trim().toLowerCase();
>   if (!/^[a-z0-9-]+(\.[a-z0-9-]+)+$/.test(domain)) {
>     throw createError({ statusCode: 400, statusMessage: "Invalid domain" });
>   }
>
>   const counter = `brand-lookups:${session.id}:${new Date().toISOString().slice(0, 10)}`;
>   const used = (await useStorage().getItem<number>(counter)) ?? 0;
>   if (used >= DAILY_LOOKUPS_PER_SESSION) {
>     throw createError({ statusCode: 429, statusMessage: "Daily lookup limit reached" });
>   }
>   await useStorage().setItem(counter, used + 1);
>
>   const response = await fetch(
>     `https://api.logo.dev/v2/brands?domain=${encodeURIComponent(domain)}`,
>     {
>       headers: { Authorization: `Bearer ${useRuntimeConfig().logoDevSecretKey}` },
>       signal: AbortSignal.timeout(20_000),
>     },
>   );
>
>   if (response.status !== 200) {
>     setResponseStatus(event, response.status);
>     return null;
>   }
>
>   const { data } = await response.json();
>   return { name: data.name, logo: data.logo?.url ?? null };
> });
> ```
>
> Your components call `/api/brand?domain=stripe.com`. The route returns the brand's name and logo URL, or `null` with the Logo.dev status. A request with no session returns `401`, and a request over the daily limit returns `429`.
>
> `useStorage()` keeps the count in server memory, so the count resets when the server restarts. If you run more than one server, [mount a shared storage driver](https://nitro.build/guide/storage), such as Redis. Logo.dev bills each new domain once per 24 hours. See [what is billed](https://www.logo.dev/docs/platform/credits.md#what-is-billed).

## Handle each status

Only a `200` carries a brand. The samples return no brand for `202` and `404` (Java throws), and throw for any other status.

| Status | Meaning | What to do |
| - | - | - |
| `200` | The brand is ready. | Use `data`. `data.logo` can be `null`. |
| `202` | Logo.dev is indexing the brand. | Retry after the `Retry-After` seconds. |
| `404` | No brand matches. | Show the [CDN logo](#show-a-logo) or a placeholder. |

`data.logo.url` is a signed download URL that stops working after about 24 hours. Download the file if you need to keep it. See [images](https://www.logo.dev/docs/brand/introduction.md#images). For the full response, see [Brand](https://www.logo.dev/docs/brand/introduction.md).

## Next steps

> [card] [CDN parameters](https://www.logo.dev/docs/logo-images/introduction.md)
>
> Size, format, theme, and lookups by name, ticker, or ISIN.

> [card] [Brand](https://www.logo.dev/docs/brand/introduction.md)
>
> Every identifier, field, and status the API returns.

> [card] [Keys and restrictions](https://www.logo.dev/docs/platform/api-keys.md)
>
> Create keys and limit a publishable key to your domains.

> [card] [Rate limits](https://www.logo.dev/docs/platform/rate-limits.md)
>
> Check the limits before you ship.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.