#

downloader

Discord profile pictures in full resolution

Free Discord tool

Discord PFP Downloader

View and download anyone's Discord profile picture and banner in full size HD. Discord shows avatars at 128 pixels. The original is stored at up to 4096, and this gets you that file.

17 to 20 digits. A profile link works too, the ID is read out of it.

  • No login or account
  • Avatars and banners
  • Animated GIFs supported
  • Free API included

How to find a Discord user ID

Discord hides IDs behind a developer setting. It takes about fifteen seconds to turn on, and you only do it once.

  1. 1

    Open user settings

    Click the gear icon next to your name, bottom left of the Discord window.

  2. 2

    Turn on Developer Mode

    Go to Advanced in the sidebar and switch Developer Mode on.

  3. 3

    Copy the ID

    Right click any user, or long press on mobile, and choose Copy User ID.

  4. 4

    Paste it above

    You will get the avatar, the banner, and the account creation date.

What this Discord profile picture viewer does

Full resolution avatars

Discord's client renders profile pictures at 128 pixels. The original upload sits on Discord's CDN at up to 4096. Every size in between is one click away.

Animated avatars

Nitro users can set animated avatars. Those are detected automatically and displayed as animated WebP, then saved as a GIF file, so they never arrive as a frozen still frame.

Profile banners

If the account has a banner, you get that too, at full width and in its original format.

Account creation date

Every Discord ID has a timestamp built into it. That means the exact date and time an account was made, down to the millisecond.

A free API

Everything on this page is available as a plain GET request with no key. Drop it into a bot, a website, or a script.

Works on any device

Phone, tablet, or desktop. Nothing to install and no account to create.

Frequently asked questions

How do I download someone's Discord profile picture?

Copy their user ID using the four steps above, paste it into the box, and press Look up. Use the Download avatar button to save the largest available version to your device.

Can I see a Discord PFP in full size HD?

Yes. Discord stores the original upload and serves it at any power of two from 16 up to 4096 pixels. The client just requests a small one to save bandwidth. This tool asks for the large one.

Can I find someone by their Discord username?

No. Discord offers no public endpoint that turns a username into an ID, on purpose, because that would let anyone walk the entire user base. This tool takes an ID and nothing else, and keeps no directory of names. Anything claiming to do username search is querying a scraped database.

Does this work with animated avatars?

Yes. Animated avatar hashes begin with a_. Those are displayed as animated WebP, which Discord recommends and which is several times smaller than the same GIF, and the download button saves them as a GIF file.

Will the person know I looked them up?

No. Nothing is sent to that account. The request goes to Discord's public API and the person receives no notification of any kind.

Can I download a Discord banner?

Yes, when the account has one. Banners are a Nitro feature, so most accounts will show Not set instead.

Is it free, and can I use the API in my own project?

Both free. The API needs no key and allows cross origin requests. See the API reference for JavaScript and Python examples. Please keep to the published rate limit of 60 requests per minute, or run your own copy.

Why does it need a user ID instead of a name?

Usernames can change at any time. IDs never do. Discord's own API is built around IDs for that reason, and every avatar URL contains one.

API reference

A free Discord avatar API. Five endpoints, every one a plain GET, no key and no auth header. Responses allow cross origin requests, so you can call them straight from a browser.

Start here

If you read one section, read this one. There is a single pattern that makes this API fast for your users and free for everyone, and it is three steps:

  1. 1

    Ask for the profile once

    Call /api/v1/users/:id and keep two things: the user's ID and avatar.hash.

  2. 2

    Store the hash, not the URL

    A hash is stable and lets you rebuild the image at any size later. Comparing hashes also tells you when someone changed their picture.

  3. 3

    Point your image tag at Discord

    Render avatar.url directly. Discord's CDN serves the bytes, so this server is never in the path again.

Why it matters: a URL containing the avatar hash can never change contents, so Discord serves it with Cache-Control: public, max-age=31536000 and the visitor's browser keeps it for a year. Your page keeps rendering avatars even if this service is down, and neither of us pays for the image bytes.

Do this

  • Fetch the profile once per user, then cache it
  • Put avatar.url in your image tag
  • Store avatar.hash in your database
  • Ask for the size you actually display
  • Use the batch route for a whole member list
  • Call from the browser so limits are per visitor

Avoid this

  • Calling the API on every page render
  • Putting this domain in a long lived image tag
  • Storing whole URLs that go stale
  • Requesting 4096 for a 40 pixel avatar
  • Looping one request per user
  • Proxying images through your own server

That last one is worth spelling out. Discord's CDN sends Access-Control-Allow-Origin: * and applies no hotlink protection, so a browser anywhere can load or read those images with no proxy of your own. If you were planning to pipe avatars through your backend, you do not need to.

Quick start

curl https://pfpgrab.com/api/v1/users/992511112223294201

Every response is JSON. Every failure is JSON in the same shape, with a stable error.code you can branch on. Image bytes always come from Discord's CDN, never from this server.

Which route to use

Two ways to get to an image. They are not equivalent, and picking the right one is most of what separates a fast integration from a slow one.

Question Profile JSON Avatar redirect
Route /users/:id /users/:id/avatar
Hits this server Once per user, then cache Every single image render
Cacheable forever Yes, the URL holds the hash No, only 5 minutes
Works if we are down Yes, once you have the hash No
Extra data Name, banner, created date None
Best for Apps, sites, bots, anything real A quick one liner, a README badge

The redirect exists because it is genuinely convenient when you have an ID and nowhere to put a hash. It is not the route to build a product on.

Endpoints

GET /api/v1/users/:id

The endpoint to build on. Returns the full profile with CDN URLs you can use directly.

Name In Description
id path Discord user ID. 17 to 20 digits.
size query Power of two from 16 to 4096. Sets the width of avatar.url and banner.url. Defaults to 1024. Anything else returns invalid_size.
{
  "id": "992511112223294201",
  "username": "exampleuser",
  "display_name": "Example User",
  "is_bot": false,
  "created_at": "2022-07-01T19:24:49.118Z",
  "accent_color": "#5865f2",
  "avatar": {
    "hash": "a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6",
    "url": "https://cdn.discordapp.com/avatars/992511112223294201/a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6.webp?size=1024",
    "static_url": "https://cdn.discordapp.com/avatars/992511112223294201/a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6.png?size=1024",
    "full_size_url": "https://cdn.discordapp.com/avatars/992511112223294201/a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6.webp?size=4096",
    "size": 1024,
    "animated": false,
    "is_default": false
  },
  "banner": null
}

url is WebP. Measured against a real avatar at 1024 that is 5.4 KB where the PNG is 41 KB, for a picture the eye cannot tell apart, and every browser in use accepts it. static_url is the PNG if you need one.

An animated avatar adds animated=true to the url and carries an extra gif_url for saving:

"avatar": {
  "hash": "a_1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6e",
  "url": "https://cdn.discordapp.com/avatars/.../a_1b2c....webp?size=1024&animated=true",
  "static_url": "https://cdn.discordapp.com/avatars/.../a_1b2c....png?size=1024",
  "full_size_url": "https://cdn.discordapp.com/avatars/.../a_1b2c....webp?size=4096&animated=true",
  "gif_url": "https://cdn.discordapp.com/avatars/.../a_1b2c....gif?size=4096",
  "size": 1024,
  "animated": true,
  "is_default": false
}
GET /api/v1/users?ids=a,b,c

Up to 25 users in one request. Use this instead of a loop: one call for a whole member list is faster for you and much cheaper for this server.

Name In Description
ids query Comma separated user IDs. Duplicates are collapsed. Maximum 25. Repeating the parameter works too, so ?ids=1&ids=2 and ?ids=1,2 are the same request.
size query Applies to every profile in the response.

One bad ID does not fail the batch. The status stays 200 and each ID lands in either data or errors. A batch consumes one unit of rate limit per ID, so the cost lands on whoever creates it.

{
  "requested": 3,
  "returned": 2,
  "data": [
    { "id": "992511112223294201", "username": "exampleuser" },
    { "id": "80351110224678912", "username": "anotheruser" }
  ],
  "errors": [
    {
      "id": "000000000000000000",
      "error": {
        "code": "user_not_found",
        "message": "No Discord account has that ID."
      }
    }
  ]
}
GET /api/v1/users/:id/avatar

A 302 to the image on Discord's CDN, so it works inside an image tag as is. Convenient, but see Which route to use before you reach for it.

Name In Description
id path Discord user ID.
size query Power of two from 16 to 4096. Defaults to 1024.
<img src="https://pfpgrab.com/api/v1/users/992511112223294201/avatar?size=256">

Both Access-Control-Allow-Origin: * and Cross-Origin-Resource-Policy: cross-origin are set, which is what lets that tag work on any domain. Setting only the first is a common mistake and it is invisible until someone tries to embed it: CORS never applies to a no-cors load, and only the second does.

GET /api/v1/users/:id/banner

The same idea for banners, with the same size parameter. Returns 404 no_banner when the account has not set one, which is most accounts, so handle that case rather than letting an image tag break.

GET /api/health

Status, version, and uptime. A working health check for any platform that wants one. Never cached.

Response fields

Field Notes
display_name Falls back to username when the account has no display name set.
created_at Decoded from the ID itself. No extra request was needed to produce it.
accent_color Hex string or null. Discord sends an integer, we convert it.
avatar.hash The raw CDN hash. Store this. Compare it between calls to detect a change without downloading anything.
avatar.url Ready for an image tag. Always WebP, with animated=true when the avatar is animated. This is the one to use.
avatar.static_url Always PNG, so never animated. Use it for a list that should not have twenty things moving in it, or anywhere WebP is not accepted.
avatar.full_size_url The original at 4096 as WebP, ignoring size. Note the CDN never upscales, so on a small source picture this is the same bytes as a smaller size.
avatar.gif_url Present only when animated is true. Use it for saving a file. The CDN rejects a GIF request on a static avatar, which is why the field is absent rather than null.
avatar.size Echoes the size you asked for, or null for default avatars.
avatar.is_default True when the account has set no picture. Discord serves its six built in avatars at one fixed size, so size is ignored for those.
banner Same shape as avatar, or null. Banners are a Nitro feature.

Why WebP for animated images: Discord recommends it, because an avatar originally uploaded as WebP or AVIF does not convert cleanly to GIF, while WebP works whatever the source was. It is also far smaller. A typical avatar at 256 pixels is around 40 KB as PNG and around 5 KB as WebP.

This shape is deliberately not Discord's own, so a change on their side does not become a breaking change here. v1 is additive: new fields may appear, so parse defensively and ignore what you do not recognise. Removing or renaming a field would mean v2.

Recipes

Show a Discord avatar next to a username on your site. This is the most common use, and the whole job is four lines. Fetch once when the user links their account, store the pair, and render from your own database from then on.

// Once, when the user gives you their Discord ID
const profile = await getProfile(discordId);
await db.users.update(userId, {
  discordId,
  avatarHash: profile.avatar.hash,
  avatarAnimated: profile.avatar.animated,
});

// Every render after that. No call to this API at all.
function avatarSrc(user, size = 64) {
  if (!user.avatarHash) return "https://cdn.discordapp.com/embed/avatars/0.png";

  const extension = user.avatarAnimated ? "webp" : "png";
  const animated = user.avatarAnimated ? "&animated=true" : "";

  return `https://cdn.discordapp.com/avatars/${user.discordId}/${user.avatarHash}.${extension}?size=${size}${animated}`;
}

Ask for the size you display. ?size=64 is about 4 KB and ?size=1024 is about 40 KB, and a room full of 40 pixel avatars at 1024 is a lot of wasted load on your own visitors. Refresh the stored hash occasionally, since people change their picture.

Render a member list. One batch call, not twenty five lookups. Split anything longer than 25 into chunks.

const response = await fetch(
  `${BASE}/api/v1/users?ids=${ids.slice(0, 25).join(",")}&size=64`,
);
const { data, errors } = await response.json();

// data holds the profiles that resolved, errors the ones that did not.
// The status is 200 either way, so check errors rather than response.ok.

Let someone save an avatar. Fetch the CDN URL in the browser and save the blob. Nothing touches this server, so it costs nothing however many people press the button.

async function saveAvatar(profile) {
  // Animated avatars get saved as GIF, which is what people expect from a file.
  // Everything else keeps the format the URL already names.
  const url = profile.avatar.gif_url ?? profile.avatar.full_size_url;
  const extension = new URL(url).pathname.split(".").pop();

  const blob = await (await fetch(url)).blob();
  const objectUrl = URL.createObjectURL(blob);

  const link = document.createElement("a");
  link.href = objectUrl;
  link.download = `${profile.username}.${extension}`;
  link.click();

  setTimeout(() => URL.revokeObjectURL(objectUrl), 30_000);
}

Code examples

Works in the browser and in Node 18 or newer.

const BASE = "https://pfpgrab.com";

class ProfileError extends Error {
  constructor({ code, message, retry_after }) {
    super(message);
    this.code = code;
    this.retryAfter = retry_after ?? 0;
  }
}

async function getProfile(userId, size = 1024) {
  const response = await fetch(`${BASE}/api/v1/users/${userId}?size=${size}`);
  const body = await response.json();

  // Every failure is { "error": { "code", "message" } }.
  // Branch on code, never on the wording of message.
  if (!response.ok) throw new ProfileError(body.error);

  return body;
}

// Codes that mean "busy right now" rather than "you asked for the wrong
// thing". Those are the only ones worth trying again.
const RETRYABLE = new Set([
  "server_busy",
  "upstream_rate_limited",
  "upstream_timeout",
  "upstream_error",
]);

async function getProfileWithRetries(userId, attempts = 3) {
  for (let attempt = 1; ; attempt += 1) {
    try {
      return await getProfile(userId);
    } catch (err) {
      if (attempt >= attempts || !RETRYABLE.has(err.code)) throw err;

      const wait = err.retryAfter || 2 ** (attempt - 1);
      await new Promise((resolve) => setTimeout(resolve, wait * 1000));
    }
  }
}

try {
  const profile = await getProfileWithRetries("992511112223294201");
  console.log(profile.display_name, profile.avatar.hash);
} catch (err) {
  if (err.code === "user_not_found") console.log("No such account.");
  else if (err.code === "rate_limited") console.log("Slow down.");
  else console.error(err.code, err.message);
}

Uses requests. Install it with pip install requests.

import requests

BASE = "https://pfpgrab.com"


class ProfileError(RuntimeError):
    def __init__(self, code: str, message: str, retry_after: int = 0):
        super().__init__(message)
        self.code = code
        self.retry_after = retry_after


def get_profile(user_id: str, size: int = 1024) -> dict:
    """Fetch one Discord profile."""
    response = requests.get(
        f"{BASE}/api/v1/users/{user_id}",
        params={"size": size},
        timeout=10,
    )

    body = response.json()
    if not response.ok:
        error = body.get("error", {})
        raise ProfileError(
            error.get("code", "unknown"),
            error.get("message", ""),
            error.get("retry_after", 0),
        )

    return body


def get_profiles(user_ids: list[str]) -> dict:
    """Up to 25 profiles in one request."""
    response = requests.get(
        f"{BASE}/api/v1/users",
        params={"ids": ",".join(user_ids[:25])},
        timeout=15,
    )
    response.raise_for_status()
    return response.json()


def save_avatar(profile: dict, path: str) -> None:
    """Download straight from Discord's CDN, not through the API."""
    avatar = profile["avatar"]
    url = avatar.get("gif_url") or avatar["full_size_url"]

    with requests.get(url, stream=True, timeout=30) as response:
        response.raise_for_status()
        with open(path, "wb") as file:
            for chunk in response.iter_content(8192):
                file.write(chunk)


if __name__ == "__main__":
    try:
        profile = get_profile("992511112223294201")
    except ProfileError as err:
        print(f"Lookup failed ({err.code}): {err}")
    else:
        print(profile["display_name"], profile["created_at"])
        save_avatar(profile, "avatar.png")

A hook you can drop into any component.

import { useEffect, useState } from "react";

const BASE = "https://pfpgrab.com";

export function useDiscordProfile(userId, size = 1024) {
  const [profile, setProfile] = useState(null);
  const [error, setError] = useState(null);
  const [loading, setLoading] = useState(false);

  useEffect(() => {
    if (!userId) return;

    // Cancel the request if the component unmounts or userId changes,
    // so a slow response cannot overwrite a newer one.
    const controller = new AbortController();

    setLoading(true);
    setError(null);

    fetch(`${BASE}/api/v1/users/${userId}?size=${size}`, {
      signal: controller.signal,
    })
      .then(async (response) => {
        const body = await response.json();
        if (!response.ok) throw body.error;
        setProfile(body);
      })
      .catch((err) => {
        // { code, message } from the API, or a DOMException on abort.
        if (err.name !== "AbortError") setError(err.code ? err : null);
      })
      .finally(() => setLoading(false));

    return () => controller.abort();
  }, [userId, size]);

  return { profile, error, loading };
}

Errors

Every failure returns the matching HTTP status and a body shaped like this:

{
  "error": {
    "code": "user_not_found",
    "message": "No Discord account has that ID."
  }
}

code is stable and safe to branch on. message is for humans and may be reworded at any time, so do not match on it. Some codes carry extra fields alongside those two, such as retry_after on a rate limit or valid_sizes on a bad size. Anything with a retry_after also sends it as a Retry-After header.

Errors that state a fact about the request, such as user_not_found or invalid_size, are sent Cache-Control: public, max-age=60, because they will say the same thing a second later. Everything else is no-store, so one bad moment at Discord is never frozen into a cache between here and your users.

Code HTTP Meaning and what to do
invalid_user_id 400 Not 17 to 20 digits. Fix the input, do not retry.
invalid_size 400 size is not a power of two from 16 to 4096. The allowed list comes back in valid_sizes.
missing_ids 400 A batch call arrived with no ids parameter.
too_many_ids 400 More than 25 IDs in one batch. Split the list.
user_blocked 403 The owner of that ID asked to be excluded from this service. Permanent, and nothing to retry.
user_not_found 404 No account matches. Tell the user, do not retry.
no_banner 404 The account exists but has set no banner. Expected for most accounts.
endpoint_not_found 404 No route matches that path. Check the spelling.
method_not_allowed 405 Everything here is a GET.
rate_limited 429 You have used your budget. Wait retry_after seconds. Do not retry sooner, it only spends more of it.
upstream_rate_limited 429 Discord is throttling this service, not you. Retry with backoff.
upstream_error 502 Discord replied with something unexpected. Retry once.
server_busy 503 Too many lookups are already queued for Discord. Retry after the wait it gives you.
upstream_timeout 503 Discord did not respond in time. Retry with backoff.
internal_error 500 A bug on this end. Retrying will probably not help.

Limits

  • 60 requests per minute per client. A batch call costs one unit per ID, so 25 IDs in one request costs 25.
  • Every response carries X-RateLimit-Limit, X-RateLimit-Remaining and X-RateLimit-Reset. Read them and throttle yourself rather than waiting for a 429.
  • Treat those three as advisory. Successful responses are cacheable, and a CDN that serves you one from its cache hands you the header values that were current when it was stored, which may be another caller's. A 429 with Retry-After is the authoritative signal.
  • Successful lookups are cached for 10 minutes, so repeat calls for the same ID are instant and cost nothing.
  • Identical requests arriving at the same moment are merged into a single call to Discord.
  • Call from the browser where you can, so the limit applies per visitor. Calling from your own backend means every one of your users shares a single bucket.
  • Set a timeout on your side. 10 seconds is plenty.
  • The real ceiling is not this server. Discord allows roughly 50 requests per second on a single bot token, which is why caching a hash on your side matters more than any limit here.

Run your own

This project is open source and MIT licensed. If you are building something that needs its own limits, or you would rather not depend on someone else's uptime, run your own copy. It is a single Node process with four dependencies and no build step.

git clone https://github.com/mohdmahmodi/discord-pfp-api.git
cd discord-pfp-api
npm install
cp .env.example .env    # paste your own bot token in
npm start

You bring your own Discord bot token. Create an application in the developer portal, open the Bot tab, press Reset Token, and put it in .env. The bot needs no permissions, no privileged intents, and no server memberships, because /users/{user_id} reads public profile data only.

Two settings to get right before you expose it. Set TRUST_PROXY_HOPS to match how many proxies sit in front of the process, since it decides which address the rate limiter counts against. And leave TRUST_CLOUDFLARE off until your origin refuses connections that did not arrive through Cloudflare. The README covers both, and the startup banner prints what is in use.

Terms and privacy

Plain language, no clauses designed to be skipped. If something here is unclear, assume the reading that gives you more privacy.

Last updated 26 July 2026

What this service can see

  • User ID
  • Username and display name
  • Avatar image and hash
  • Banner image, if set
  • Profile accent colour
  • Account creation date
  • Whether the account is a bot

What it cannot see

  • Email address
  • Phone number
  • Messages or direct messages
  • Servers the account is in
  • Friend lists
  • IP address or location
  • Online status or activity

01. What we can see

This service reads public profile data through Discord's official API using a bot token with no permissions, no privileged intents, and no server memberships. The fields in the table above are the entire response. The private fields are not withheld by policy, they are simply not present in the data Discord returns.

Looking someone up sends nothing to that account. They receive no notification and have no way to tell it happened.

A lookup needs a user ID. There is no username search here, and no list of names is kept, because Discord deliberately provides no way to turn a name into an ID and building one would mean assembling a directory of people. Sites advertising username search are querying scraped databases put together without consent.

02. Your data

There is no account system, so there is nothing to sign up for and nothing to delete. Specifically:

  • Lookup results are cached in memory for 10 minutes, stay usable for up to 50 minutes more while they refresh, and are dropped after that. Nothing about a looked up profile is written to disk.
  • Your recent lookups are stored in your own browser, not on the server. Clearing them removes them completely.
  • Avatar and banner images are served to you by Discord's CDN directly. Downloads are assembled in your browser, so no image ever passes through this server.
  • No cookies are set. No analytics or advertising scripts run.
  • Request logs are kept only as long as needed to operate and secure the service.

03. Acceptable use

Do not use this to harass, impersonate, stalk, or compile records on other people. Doing so breaks Discord's Terms of Service as well as these, and access may be blocked without notice.

Automated use is limited to 60 requests per minute per client. Stay within it and you will never see a problem. Scraping at volume is blocked. If you need more, the project is open source and you can run your own copy on your own bot token.

04. Image rights

Avatars and banners belong to the people who uploaded them, not to this service and not to you. Downloading an image here does not give you a licence to republish it, use it commercially, or pass it off as your own. You are responsible for what you do with anything you save.

This project is not affiliated with, endorsed by, or sponsored by Discord Inc. Discord is a trademark of Discord Inc.

The service is provided as is, with no warranty. It depends on Discord's API, which can change or become unavailable at any time and without notice.

The full privacy policy, including who else your browser talks to while this page is open, is in #privacy.

Privacy policy

This is the full policy for pfpgrab.com and the API it serves. It is written to be read, not to be skipped. Where a sentence could be read two ways, take the one that gives you more privacy, because that is the one that was meant.

Last updated 26 July 2026 · Applies to pfpgrab.com and every endpoint under /api

01. The short version

There are no accounts, no cookies set by this site, no analytics, no advertising, and no tracking of any kind. Nothing about a profile you look up is written to disk. Everything this service holds lives in the memory of one process and is gone within an hour, or immediately if the process restarts.

What this service does

  • Reads public Discord profile fields by user ID
  • Holds the result in memory for up to an hour
  • Counts requests per IP address to enforce a rate limit
  • Returns links that point at Discord's own servers
  • Stores your recent lookups in your own browser

What it never does

  • Set a cookie, or run an analytics or advertising script
  • Write profile data, images, or IP addresses to disk
  • Build a searchable directory of Discord users
  • Sell, licence, or share data with anyone
  • Tell the person you looked up that it happened
  • Store or proxy a single byte of any avatar image

02. Who runs this

pfpgrab is run by Mohd Mahmodi, an individual, as a free and non-commercial project. There is no company behind it and no revenue attached to it. For anything in this policy, including a deletion request, the contact is [email protected].

The entire server is open source. If you would rather verify a claim in this policy than trust it, the code that makes each one true is at github.com/mohdmahmodi/discord-pfp-api. Every statement below about what is stored and for how long is a line in server.js you can read for yourself.

03. If your profile was looked up

Somebody entering your Discord user ID here causes this server to ask Discord's official API for your public profile. That is the same data Discord shows to anyone who can see your profile in the app. Discord is the source; this service is a reader.

The complete set of fields involved:

Field What it is
id Your Discord user ID, which is what was entered
username Your Discord username
display_name Your display name, if you have set one
avatar The hash of your avatar, which forms its image URL
banner The hash of your banner, if you have one
accent_color Your profile accent colour
bot Whether the account is a bot
created_at Not from Discord. Decoded from the first 42 bits of your ID, which are a timestamp

Your email address, phone number, messages, servers, friends, IP address, online status, and activity are not in that response. They are not withheld here by policy: Discord simply does not return them to this kind of request, and this service holds no permissions, no privileged intents, and no server memberships that would change that.

You receive no notification, because nothing is sent to your account. A lookup is a read of Discord's public API and is invisible from your side.

There is no way to search by username here, and no list of names is kept. Discord deliberately provides no way to turn a name into an ID, and building one would mean assembling a directory of people. Sites that advertise username search are querying scraped databases put together without consent, and this is not one of them.

04. If you are visiting the site

Your IP address reaches this server the way it reaches any website, as a property of the connection. It is used for exactly one thing: counting requests so that one client cannot exhaust the service for everyone else. The counter lives in memory, resets every 60 seconds, and is never written anywhere.

  • No cookies are set by this site. There is no session, no login, and nothing to consent to.
  • No analytics. There is no Google Analytics, no Plausible, no Fathom, no pixel, and no error reporting service.
  • Your recent lookups stay in your browser. They are kept in localStorage under the key dpa:recent, never sent to the server, and cleared for good by the Clear button in the sidebar or by clearing site data.
  • Downloads never touch this server. When you save an avatar, your browser fetches the image from Discord's CDN and assembles the file locally. No image passes through this machine, which is also why the service costs almost nothing to run.

05. How long anything is kept

Everything below is memory only. This service runs no database and writes no data files. Restarting the process erases all of it, and deployments restart the process.

What Where How long
A looked up profile Server memory 10 minutes fresh, then up to 50 more while it refreshes
An ID that matched no account Server memory 60 seconds
Rate limit counter for an IP Server memory 60 seconds, then reset
Your recent lookups Your browser Until you clear them
Avatar and banner images Never held here at all

Operational logs record errors and the fact that the server is running. They do not record user IDs or IP addresses, and they are rotated by the operating system rather than kept.

06. Who else sees you when you use this site

This is the part most privacy policies leave vague, so here it is plainly. Loading this page causes your browser to talk to a few other companies, and each of them sees your IP address because that is how the web works. Nothing is shared with them by this service; they see you because your browser connects to them directly.

Who Why your browser contacts them
cdn.discordapp.com Serves every avatar and banner image. Discord sees the request for each image you view or save
cloudflare.com Sits in front of this site as a CDN and firewall. Cloudflare sets a __cf_bm bot management cookie of its own and keeps request logs under its own policy
cdnjs.cloudflare.com Serves the icon font and the code highlighter used on this page. Each file is pinned to a hash, so a modified file is refused by your browser
fonts.googleapis.com Serves the typefaces the page is set in. Google sees the request

Those four are the complete list. There is no fifth. If you want none of them, the project is open source and a self hosted copy can drop the fonts and the icon set entirely.

07. Using the API in your own project

If you build on this API, you become responsible for what you do with what it returns. Two things are worth stating clearly.

The URLs returned point at Discord's CDN, not at this service. When your visitors load one, their browsers talk to Discord directly and Discord sees them. That is the correct design and it is why the images stay fast and free, but it does mean an avatar you embed is a request to Discord made by your visitor.

An avatar URL contains a hash that changes whenever the person changes their picture. If you store one, refresh it periodically rather than keeping it forever, so that somebody who changes or removes their avatar stops being shown the old one by your site.

Do not use this API to compile records on people, to enumerate accounts by walking through IDs, or to build a directory. Those break Discord's Developer Policy as well as these terms, and access can be blocked without notice.

08. Your rights

Under the GDPR, the UK GDPR, the CCPA, and similar laws you have rights of access, correction, deletion, restriction, portability, and objection. This service is unusual in how easy those are to satisfy: there is no account to close, no profile to export, and nothing about you stored for longer than an hour.

  • Deletion. Email [email protected] with your Discord user ID and the cached copy will be dropped immediately. In practice it will have expired on its own before the message arrives.
  • Blocking future lookups. Ask, and your ID will be refused by this service from then on. You do not have to explain why. It is added to the BLOCKED_USER_IDS list the server reads at startup, after which that ID returns 403 user_blocked and is never sent to Discord again.
  • Access. What is held is exactly the table in section 03 and nothing else. There is no additional profile to disclose.
  • The source. Changing what this service can see about you is done at Discord, not here. Changing your avatar changes its hash, and the old URL stops describing you.

The lawful basis for processing is legitimate interest: showing a person the public profile picture belonging to an ID they already have, at their own request. No profiling, automated decision making, or advertising is involved. Requests are answered within 30 days and normally within a day.

09. Children

This service is not directed at children under 13, and Discord itself requires users to be at least 13. No age is collected, because no account exists to attach one to. If you believe a child's data is being handled here in a way it should not be, write to [email protected] and it will be dealt with straight away.

10. Changes to this policy

The date at the top changes whenever this document does. Because the policy lives in the repository, every edit is a commit with a diff you can read, so there is no version of it you cannot check. A change that makes this service collect more than it does today will be described here rather than quietly folded in.

11. Contact

Getting in touch

Privacy, deletion, blocking, and anything else in this document: [email protected].
Public or informal: @mohdmahmodi on X.
Security problems: please read SECURITY.md first, then email rather than opening a public issue.

This project is not affiliated with, endorsed by, or sponsored by Discord Inc. Discord is a trademark of Discord Inc. Avatars and banners belong to the people who uploaded them, and nothing here grants a licence to republish them.