> ## 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 CDN serves images from img.logo.dev. The Logo API is the Brand API and the Search API, which return JSON from api.logo.dev/v2. The Transaction Enrichment API (beta) is a separate service at https://www.logo.dev/api/transaction with its own beta key. Credits is the Logo API billing unit. Never recommend the deprecated v1 routes (/search, /brand, /describe, /logo/*) or the Describe API. Use /v2/brands and /v2/search.
> 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.

# Add company logos to directory and profile pages

> A real logo and description, so every listing looks complete. Add company logos to integration marketplaces, job boards, and vendor directories.

export const JobBoardDemo = () => {
  const TOKEN = "live_6a1a28fd-6420-4492-aeb0-b297461d9de2";
  const SOCIAL_ICONS = {
    github: <svg aria-hidden="true" fill="currentColor" height="14" viewBox="0 0 16 16" width="14" xmlns="http://www.w3.org/2000/svg">
        <title>GitHub</title>
        <path d="M8 0a8 8 0 0 0-2.53 15.59c.4.07.55-.17.55-.38v-1.34c-2.23.48-2.7-1.07-2.7-1.07-.36-.93-.89-1.18-.89-1.18-.73-.5.05-.49.05-.49.8.06 1.23.83 1.23.83.72 1.23 1.87.87 2.33.67.07-.52.28-.87.5-1.07-1.78-.2-3.64-.89-3.64-3.95 0-.87.31-1.59.83-2.15-.08-.2-.36-1.02.08-2.12 0 0 .67-.22 2.2.82a7.6 7.6 0 0 1 4 0c1.53-1.04 2.2-.82 2.2-.82.44 1.1.16 1.92.08 2.12.52.56.83 1.28.83 2.15 0 3.07-1.87 3.75-3.65 3.95.29.25.54.73.54 1.48v2.2c0 .21.15.46.55.38A8 8 0 0 0 8 0Z" />
      </svg>,
    linkedin: <svg aria-hidden="true" fill="currentColor" height="14" viewBox="0 0 16 16" width="14" xmlns="http://www.w3.org/2000/svg">
        <title>LinkedIn</title>
        <path d="M3.6 5.3H.8V14h2.8V5.3ZM2.2 1a1.6 1.6 0 1 0 0 3.2 1.6 1.6 0 0 0 0-3.2ZM15.2 14v-4.8c0-2.4-.5-4.1-3.3-4.1-1.3 0-2.2.7-2.6 1.4h-.04V5.3H6.5V14h2.8V9.7c0-1.1.2-2.2 1.6-2.2 1.4 0 1.5 1.3 1.5 2.3V14h2.8Z" />
      </svg>,
    twitter: <svg aria-hidden="true" fill="currentColor" height="14" viewBox="0 0 16 16" width="14" xmlns="http://www.w3.org/2000/svg">
        <title>X</title>
        <path d="M9.5 6.8 15.4 0h-1.4L8.9 5.9 4.8 0H0l6.2 9-6.2 7.2h1.4l5.4-6.3 4.3 6.3H16L9.5 6.8Zm-1.9 2.2-.6-.9L1.9 1h2.1l4 5.8.6.9 5.2 7.5h-2.1L7.6 9Z" />
      </svg>,
    youtube: <svg aria-hidden="true" fill="currentColor" height="14" viewBox="0 0 16 16" width="14" xmlns="http://www.w3.org/2000/svg">
        <title>YouTube</title>
        <path d="M15.7 4.3a2 2 0 0 0-1.4-1.4C13 2.5 8 2.5 8 2.5s-5 0-6.3.4A2 2 0 0 0 .3 4.3C0 5.6 0 8 0 8s0 2.4.3 3.7a2 2 0 0 0 1.4 1.4c1.3.4 6.3.4 6.3.4s5 0 6.3-.4a2 2 0 0 0 1.4-1.4C16 10.4 16 8 16 8s0-2.4-.3-3.7ZM6.4 10.4V5.6L10.5 8l-4.1 2.4Z" />
      </svg>,
    facebook: <svg aria-hidden="true" fill="currentColor" height="14" viewBox="0 0 16 16" width="14" xmlns="http://www.w3.org/2000/svg">
        <title>Facebook</title>
        <path d="M16 8a8 8 0 1 0-9.25 7.9v-5.59H4.72V8h2.03V6.24c0-2 1.2-3.11 3.02-3.11.88 0 1.79.16 1.79.16v1.97h-1.01c-1 0-1.31.62-1.31 1.25V8h2.22l-.35 2.31H9.25v5.59A8 8 0 0 0 16 8Z" />
      </svg>,
    instagram: <svg aria-hidden="true" fill="currentColor" height="14" viewBox="0 0 16 16" width="14" xmlns="http://www.w3.org/2000/svg">
        <title>Instagram</title>
        <path d="M8 1.44c2.14 0 2.39.01 3.23.05.78.03 1.2.17 1.49.28.37.14.64.32.92.6.28.28.46.55.6.92.11.29.25.71.28 1.49.04.84.05 1.09.05 3.23s-.01 2.39-.05 3.23c-.03.78-.17 1.2-.28 1.49-.14.37-.32.64-.6.92-.28.28-.55.46-.92.6-.29.11-.71.25-1.49.28-.84.04-1.09.05-3.23.05s-2.39-.01-3.23-.05c-.78-.03-1.2-.17-1.49-.28a2.48 2.48 0 0 1-.92-.6 2.48 2.48 0 0 1-.6-.92c-.11-.29-.25-.71-.28-1.49C1.45 10.39 1.44 10.14 1.44 8s.01-2.39.05-3.23c.03-.78.17-1.2.28-1.49.14-.37.32-.64.6-.92.28-.28.55-.46.92-.6.29-.11.71-.25 1.49-.28C5.61 1.45 5.86 1.44 8 1.44ZM8 0C5.83 0 5.55.01 4.7.05c-.85.04-1.43.17-1.94.37-.53.2-.98.48-1.42.93-.45.44-.72.89-.93 1.42-.2.51-.33 1.09-.37 1.94C.01 5.55 0 5.83 0 8s.01 2.45.05 3.3c.04.85.17 1.43.37 1.94.2.53.48.98.93 1.42.44.45.89.72 1.42.93.51.2 1.09.33 1.94.37C5.55 15.99 5.83 16 8 16s2.45-.01 3.3-.05c.85-.04 1.43-.17 1.94-.37.53-.2.98-.48 1.42-.93.45-.44.72-.89.93-1.42.2-.51.33-1.09.37-1.94.04-.85.05-1.13.05-3.3s-.01-2.45-.05-3.3c-.04-.85-.17-1.43-.37-1.94a3.92 3.92 0 0 0-.93-1.42 3.92 3.92 0 0 0-1.42-.93c-.51-.2-1.09-.33-1.94-.37C10.45.01 10.17 0 8 0Zm0 3.89a4.11 4.11 0 1 0 0 8.22 4.11 4.11 0 0 0 0-8.22Zm0 6.78a2.67 2.67 0 1 1 0-5.34 2.67 2.67 0 0 1 0 5.34Zm4.27-6.94a.96.96 0 1 0 0 1.92.96.96 0 0 0 0-1.92Z" />
      </svg>
  };
  const JOBS = [{
    role: "Senior Product Designer",
    company: "Figma",
    domain: "figma.com",
    location: "Remote",
    posted: "2h ago",
    description: "The collaborative design platform for building products: design, prototype, and gather feedback in one place.",
    socials: {
      facebook: "https://www.facebook.com/figmadesign",
      instagram: "https://www.instagram.com/figma",
      linkedin: "https://linkedin.com/company/figma",
      twitter: "https://twitter.com/figma",
      youtube: "https://www.youtube.com/@Figma"
    }
  }, {
    role: "Backend Engineer",
    company: "Stripe",
    domain: "stripe.com",
    location: "Dublin",
    posted: "5h ago",
    description: "A financial services platform to accept payments, build billing, and manage money movement.",
    socials: {
      github: "https://github.com/stripe",
      linkedin: "https://linkedin.com/company/stripe",
      twitter: "https://x.com/stripe",
      youtube: "https://www.youtube.com/@stripe"
    }
  }, {
    role: "Growth Marketer",
    company: "Notion",
    domain: "notion.so",
    location: "New York",
    posted: "1d ago",
    description: "The AI workspace where teams build agents, search across apps, and automate busywork.",
    socials: {
      linkedin: "https://linkedin.com/company/notionhq",
      twitter: "https://twitter.com/NotionHQ"
    }
  }, {
    role: "Operations Lead",
    company: "Airbnb",
    domain: "airbnb.com",
    location: "Dublin",
    posted: "2d ago",
    description: null,
    socials: {}
  }];
  const [openDomain, setOpenDomain] = useState(null);
  const openJob = JOBS.find(job => job.domain === openDomain) ?? null;
  const logoSrc = (domain, size) => `https://img.logo.dev/${domain}?token=${TOKEN}&size=${size}&retina=true&format=webp`;
  const toggle = domain => setOpenDomain(current => current === domain ? null : domain);
  return <div className="not-prose my-8 rounded-2xl border border-zinc-950/10 bg-gradient-to-br from-zinc-50 to-white p-6 shadow-sm sm:p-8 dark:border-white/10 dark:from-zinc-900 dark:to-zinc-950">
      <ul className="mx-auto max-w-md space-y-3">
        {JOBS.map(job => {
    const isOpen = job.domain === openDomain;
    return <li key={`${job.domain}-${job.role}`}>
              <button aria-expanded={isOpen} className={`flex w-full items-center gap-3 rounded-xl border bg-white p-4 text-left transition-[background-color,border-color] duration-150 focus:outline-none focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-zinc-950/20 motion-safe:active:scale-[0.99] dark:bg-zinc-900 dark:focus-visible:ring-white/20 ${isOpen ? "border-zinc-950/20 dark:border-white/20" : "border-zinc-950/10 hover:bg-zinc-50 dark:border-white/10 dark:hover:bg-zinc-800/50"}`} onClick={() => toggle(job.domain)} type="button">
                <img alt={`${job.company} logo`} className="h-10 w-10 rounded-lg object-contain" height="40" loading="lazy" src={logoSrc(job.domain, 40)} width="40" />
                <div className="min-w-0">
                  <div className="truncate text-sm font-semibold text-zinc-950 dark:text-white">
                    {job.role}
                  </div>
                  <div className="truncate text-xs text-zinc-500 dark:text-zinc-400">
                    {job.company} · {job.location}
                  </div>
                </div>
                <span className="ml-auto shrink-0 text-xs text-zinc-400">
                  {job.posted}
                </span>
              </button>
            </li>;
  })}
      </ul>

      {openJob && <div className="mx-auto mt-4 max-w-md rounded-xl border border-zinc-950/10 bg-white p-4 dark:border-white/10 dark:bg-zinc-900">
          <div className="flex items-center gap-3">
            <img alt={`${openJob.company} logo`} className="h-12 w-12 rounded-xl object-contain" height="48" key={openJob.domain} src={logoSrc(openJob.domain, 48)} width="48" />
            <div className="min-w-0">
              <div className="truncate text-base font-semibold text-zinc-950 dark:text-white">
                {openJob.company}
              </div>
              <div className="truncate text-xs text-zinc-400">
                {openJob.domain}
              </div>
            </div>
          </div>
          {openJob.description && <div className="mt-3 text-xs leading-relaxed text-zinc-600 dark:text-zinc-400">
              {openJob.description}
            </div>}
          {Object.keys(openJob.socials).length > 0 && <div className="mt-3 flex items-center gap-1 border-t border-zinc-950/5 pt-3 dark:border-white/5">
              {Object.entries(openJob.socials).filter(([network]) => SOCIAL_ICONS[network]).map(([network, url]) => <a aria-label={network} className="flex h-8 w-8 items-center justify-center rounded-lg text-zinc-500 transition-[background-color,color] duration-150 hover:bg-zinc-100 hover:text-zinc-900 focus:outline-none focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-zinc-950/20 dark:text-zinc-400 dark:hover:bg-zinc-800 dark:hover:text-white dark:focus-visible:ring-white/20" href={url} key={network} rel="noopener noreferrer" target="_blank">
                  {SOCIAL_ICONS[network]}
                </a>)}
            </div>}
        </div>}
    </div>;
};

<JobBoardDemo />

```text Use case prompt wrap theme={null}
Build directory listings with company logos, following https://www.logo.dev/docs/use-cases/directory-logos.md

Read that page and inspect this project's stack. Use this project's existing conventions.

Store a domain with each listing, and build the logo URL from that domain in a slot with a fixed size, on a small white well so that every mark looks the same on any surface. When a listing has no domain, show its initials in the well. When a visitor opens a listing, show a company profile with the description and social links, looked up on the server. While the brand is indexing, retry until the retry window ends. The profile always shows the logo and the name, even while the brand is indexing, when there is no brand, and when the request fails.

For API details, read https://www.logo.dev/docs/logo-images/introduction.md and https://www.logo.dev/docs/brand/introduction.md
```

An integrations marketplace, a job board, or a vendor directory shows the same card thousands of times, and a card with no logo looks incomplete. Put the company's current logo on each listing and a real description on each profile, so visitors trust what they read. Store one domain for each listing, and a rebrand needs no new file. Your app owns that domain, the look of the cards, and what a listing shows when Logo.dev has no data for a company.

## Store a domain with each listing

Each listing needs a name and the company's domain:

| Listing | Save | Show |
| - | - | - |
| Domain | Name and domain | The company's logo, or a [fallback image](/docs/logo-images/introduction#fallback-images) if Logo.dev has no logo |
| No domain | Name only | The company's initials in the well |
| Wrong domain | A corrected domain | Fix the listing. A wrong domain shows the logo of a different company. |

If the person who adds a listing has only a company name, let them pick the company with a [company autocomplete](/docs/use-cases/company-autocomplete), and store the domain that they pick.

## Render the listing card

Each card is one [Logo CDN](/docs/logo-images/introduction) URL next to the fields that the listing already has. The same card works as a connector tile, a job listing, or a vendor row:

<CodeGroup>
  ```tsx ListingCard.tsx expandable theme={null}
  type Listing = {
    name: string;
    domain: string | null;
    detail: string; // a category, a role and location, or a tagline
  };

  const initials = (name: string) =>
    name
      .split(/\s+/)
      .map((word) => word[0])
      .join("")
      .slice(0, 2)
      .toUpperCase();

  export function ListingCard({
    listing,
    token,
  }: {
    listing: Listing;
    token: string;
  }) {
    const { name, domain, detail } = listing;
    return (
      <div className="flex items-center gap-3 rounded-xl border border-zinc-950/10 bg-white p-4 dark:border-white/10 dark:bg-zinc-900">
        {/* A white well makes transparent dark marks and white-background
            logos look the same on any surface. */}
        <span className="flex h-10 w-10 shrink-0 items-center justify-center rounded-lg bg-white ring-1 ring-inset ring-zinc-950/5">
          {domain ? (
            <img
              alt={`${name} logo`}
              className="h-8 w-8 rounded-md object-contain"
              height={32}
              loading="lazy"
              src={`https://img.logo.dev/${domain}?token=${token}&size=32&retina=true&format=webp`}
              width={32}
            />
          ) : (
            <span aria-hidden className="text-xs font-semibold text-zinc-500">
              {initials(name)}
            </span>
          )}
        </span>
        <div className="min-w-0">
          <div className="truncate text-sm font-semibold">{name}</div>
          <div className="truncate text-xs text-zinc-500">{detail}</div>
        </div>
      </div>
    );
  }
  ```

  ```tsx Usage theme={null}
  import { ListingCard } from "./ListingCard";

  const LISTINGS = [
    { name: "Slack", domain: "slack.com", detail: "Messaging" },
    { name: "Stripe", domain: "stripe.com", detail: "Backend Engineer · Dublin" },
    { name: "Notion", domain: "notion.so", detail: "Docs and wikis" },
    { name: "Internal Webhooks", domain: null, detail: "Custom events" },
  ];

  export function Directory() {
    return (
      <div className="grid gap-3 sm:grid-cols-2">
        {LISTINGS.map((listing) => (
          <ListingCard
            key={listing.name}
            listing={listing}
            token="LOGO_DEV_PUBLISHABLE_KEY"
          />
        ))}
      </div>
    );
  }
  ```
</CodeGroup>

For a muted grid, add the [`greyscale`](/docs/logo-images/introduction#parameters) parameter to each logo URL.

## Add a company profile

The [Brand API](/docs/brand/introduction) returns the company's name, description, and social profiles from the same domain. Call it from your server with your [secret key](/docs/platform/api-keys):

```ts api/company-profile.ts expandable theme={null}
type Brand = {
  id: string;
  name: string;
  description: string | null;
  socials: Record<string, string>;
};

export async function GET(request: Request) {
  const domain = new URL(request.url).searchParams
    .get("domain")
    ?.trim()
    .toLowerCase();
  if (!domain) {
    return Response.json({ status: "error" }, { status: 400 });
  }

  const res = await fetch(
    `https://api.logo.dev/v2/brands?domain=${encodeURIComponent(domain)}`,
    { headers: { Authorization: `Bearer ${process.env.LOGO_DEV_SECRET_KEY}` } }
  );

  // Not indexed yet: https://www.logo.dev/docs/brand/introduction#handle-each-status
  if (res.status === 202) {
    // Retry timing and the retry window:
    // https://www.logo.dev/docs/platform/errors#not-found-vs-still-indexing-202
    const body = await res.json().catch(() => null);
    const pending = body?.metadata?.pending;
    const retryAfter = Number(res.headers.get("Retry-After")) || pending?.retry_after_seconds || 30;
    const expiresIn = Number(pending?.expires_in_seconds) || 0;
    return Response.json({ status: "pending", retryAfter, expiresIn }, { status: 202 });
  }
  if (res.status === 404) {
    return Response.json({ status: "not_found" });
  }
  if (!res.ok) {
    // Any other 4xx or 5xx: a problem to log, not a company with no data.
    // Status codes: https://www.logo.dev/docs/platform/errors#logo-api-status-codes
    return Response.json({ status: "error" }, { status: 502 });
  }

  const { data }: { data: Brand } = await res.json();
  // Save data.id with the listing. It stays the same if the domain changes.
  return Response.json({
    status: "ready",
    brandId: data.id,
    name: data.name,
    description: data.description,
    socials: data.socials,
  });
}
```

Store the result with the listing, and render from it:

| Result | Save | Show |
| - | - | - |
| Brand found | Brand id, description, socials, lookup time | Logo, name, description, and one link for each network in `socials` |
| Still indexing | Nothing yet | Logo and name. Show the profile when the brand is ready. |
| No brand, or request failed | The lookup time | Logo and name |

The profile component calls the route, retries while the brand is indexing, and always shows the logo and the name:

<CodeGroup>
  ```tsx CompanyProfile.tsx expandable theme={null}
  import { useEffect, useState } from "react";

  type Profile =
    | { status: "loading" }
    | { status: "pending"; retryAfter: number; expiresIn: number }
    | {
        status: "ready";
        description: string | null;
        socials: Record<string, string>;
      }
    | { status: "not_found" }
    | { status: "error" };

  export function CompanyProfile({
    company,
    domain,
    token,
  }: {
    company: string;
    domain: string;
    token: string;
  }) {
    const [profile, setProfile] = useState<Profile>({ status: "loading" });

    useEffect(() => {
      // A new domain starts over. The cleanup stops the previous domain's
      // timer and drops its late responses.
      setProfile({ status: "loading" });
      let cancelled = false;
      let timer: ReturnType<typeof setTimeout>;
      let deadline = 0;

      const lookup = async () => {
        let next: Profile;
        try {
          const res = await fetch(`/api/company-profile?domain=${encodeURIComponent(domain)}`);
          next = (await res.json()) as Profile;
        } catch {
          next = { status: "error" };
        }
        if (cancelled) {
          return;
        }
        // Retry a 202 until the brand is ready or the retry window ends.
        // The window starts at the first 202. Retry timing and the window:
        // https://www.logo.dev/docs/platform/errors#not-found-vs-still-indexing-202
        if (next.status === "pending") {
          deadline ||= Date.now() + next.expiresIn * 1000;
          // Stop when the next attempt would start after the window ends.
          if (Date.now() + next.retryAfter * 1000 >= deadline) {
            setProfile({ status: "error" });
            return;
          }
          timer = setTimeout(lookup, next.retryAfter * 1000);
        }
        setProfile(next);
      };
      lookup();
      return () => {
        cancelled = true;
        clearTimeout(timer);
      };
    }, [domain]);

    const waiting = profile.status === "loading" || profile.status === "pending";

    return (
      <div className="max-w-sm space-y-3 rounded-xl border border-zinc-950/10 bg-white p-5">
        <div className="flex items-center gap-3">
          <img
            alt={`${company} logo`}
            className="h-12 w-12 rounded-lg object-contain"
            height={48}
            src={`https://img.logo.dev/${domain}?token=${token}&size=48&retina=true&format=webp`}
            width={48}
          />
          <div className="text-base font-semibold">{company}</div>
        </div>
        {waiting && (
          <div
            aria-hidden
            className="h-10 animate-pulse rounded-md bg-zinc-100 motion-reduce:animate-none"
          />
        )}
        {profile.status === "ready" && (
          <>
            {profile.description && (
              <div className="text-sm text-zinc-600">{profile.description}</div>
            )}
            <div className="flex flex-wrap gap-3 text-xs font-medium">
              {Object.entries(profile.socials).map(([network, url]) => (
                <a className="capitalize underline" href={url} key={network}>
                  {network}
                </a>
              ))}
            </div>
          </>
        )}
      </div>
    );
  }
  ```

  ```tsx Usage theme={null}
  import { CompanyProfile } from "./CompanyProfile";

  export function CompanyPanel() {
    return (
      <CompanyProfile
        company="Stripe"
        domain="stripe.com"
        token="LOGO_DEV_PUBLISHABLE_KEY"
      />
    );
  }
  ```
</CodeGroup>

To refresh a saved profile when the company changes its brand, use [update webhooks](/docs/webhooks).

## Test the directory

Test a card and a profile for a company with full data (`stripe.com`), a logo with a white background (`notion.so`), a transparent dark mark (`github.com`), a domain with no logo (`made-up-startup.io`), a listing with no domain, and a domain that Logo.dev has not indexed yet. Make the profile request fail once, and check that the logo and the name stay on screen. Check each card in light and dark mode.

<CardGroup cols={2}>
  <Card title="Brand API" icon="swatchbook" href="/docs/brand/introduction">
    Get a brand's description, socials, colors, and images.
  </Card>

  <Card title="CRM logos" icon="address-book" href="/docs/use-cases/crm-logos">
    Put a logo and description on every account record.
  </Card>
</CardGroup>


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