PFP Vault

Developers

PFP Vault API

Use PFP Vault in your Discord bot, website, app or game: random PFPs, search, single PFPs, bundles, banners and profiles. The API is read-only, answers in JSON and only returns what is already public on the site.

Base URL: https://pfpvault.net/api/v1

Get an API key

  1. Log in and open Account → API.
  2. Fill in the short form: what you are building, roughly how many requests you expect and which data you need.
  3. An admin reviews it, usually within a few days. You get an email and the key shows up in your account.

A key looks like pfpvault-k7Qm2xR… (pfpvault- plus 40 letters and numbers). You can show, copy or replace it in your account at any time.

Right now you can also try the API without a key (120 requests per minute per IP). With a key you get higher limits and usage stats.

Authentication

Send your key in the Authorization header. That is the safest option: it does not end up in logs or browser history.

Authorization: Bearer pfpvault-YOUR_KEY

Also accepted: the header X-API-Key: pfpvault-…, or ?api_key=pfpvault-… in the URL (only for quick tests).

Keep your key secret. Use it from your server or bot, not in JavaScript that runs in visitors' browsers: anyone could copy it from there and use up your limits. Leaked? Press “New key” in your account; the old one stops working right away.

Rate limits

  • With a key: 600 requests per minute by default. Some keys also have a daily limit (resets at 00:00 UTC). Your exact limits are in your account and at /api/v1/key.
  • Without a key: 120 requests per minute per IP address.
  • Over the limit you get HTTP 429 with a Retry-After header (seconds). Wait that long, then try again.
  • Need more? Explain why via the contact page.

Errors

Errors use normal HTTP status codes and always have the same JSON shape:

{ "error": "not_found", "message": "PFP not found." }
StatuserrorMeaning
400bad_requestA parameter is wrong.
401api_key_required / invalid_keyNo key sent while one is required, or the key does not exist.
403key_revokedThe key was turned off by PFP Vault.
404not_foundThat PFP, bundle or user does not exist (or is not public).
429rate_limited / daily_limitToo many requests. Wait the number of seconds in the Retry-After header.

The PFP object

Most endpoints return PFPs in this shape. All links are absolute.

{
  "id": "hzIYyATkS7",
  "title": "Blue Fox",
  "description": "",
  "url": "https://pfpvault.net/pfp/hzIYyATkS7",
  "image": "https://pfpvault.net/api/media/6ba90e36196e0e63a1810529.webp",
  "thumbnail": "https://pfpvault.net/api/media/6ba90e36196e0e63a1810529.webp",
  "category": { "name": "Animals", "slug": "animals" },
  "tags": ["fox", "blue"],
  "color": "Blue",
  "ai_generated": false,
  "animated": false,
  "media_type": "image",
  "mime": "image/webp",
  "uploader": { "username": "vaultfan", "url": "https://pfpvault.net/u/vaultfan" },
  "downloads": 128, "likes": 31, "views": 940,
  "created_at": "2026-11-02T19:41:12.000Z",
  "credit": null
}
  • image is the full file, url the page on PFP Vault.
  • animated is true for GIFs, animated WebP and clips. media_type is video for MP4/WEBM clips: show those with a video player (Discord embeds cannot play them, link to url instead).
  • credit is set for photos from other creators: { creator, creator_url, license, license_url, source_url }. Show the creator and license wherever you show the photo.

GET/api/v1/random

Random PFP. One random approved PFP. Great for a /random command.

ParameterValueWhat it does
categorystringCategory slug, e.g. animals (see /categories)
qstringOnly PFPs matching this search term
aihide | onlyLeave out or only show AI-made PFPs
animonly | hideOnly animated, or only still images
curl -H "Authorization: Bearer pfpvault-YOUR_KEY" \
  "https://pfpvault.net/api/v1/random?category=animals&ai=hide"
{ "pfp": { …PFP object… }, "pool": 412 }

Search. Search and filter PFPs, like the homepage. An exact PFP ID as q returns just that PFP.

ParameterValueWhat it does
qstringSearch words (title, tags, category, color) or an exact ID
categorystringCategory slug
colorstringBlue, Purple, Pink, Orange, Yellow, Green, Teal, Cyan, Black, Neon
aihide | onlyAI filter
animonly | hideOnly animated, or only still images
sorttrending | new | popular | liked | randomDefault trending
limit1–50Default 25
offsetnumberSkip this many, for paging
curl -H "Authorization: Bearer pfpvault-YOUR_KEY" \
  "https://pfpvault.net/api/v1/search?q=cat&sort=new&limit=10"
{ "total": 87, "items": [{ …PFP object… }, { …PFP object… }, …] }

GET/api/v1/pfp/{id}

One PFP. A single PFP by its ID (the last part of the PFP page link).

curl -H "Authorization: Bearer pfpvault-YOUR_KEY" \
  "https://pfpvault.net/api/v1/pfp/hzIYyATkS7"
{ "pfp": { …PFP object… } }

GET/api/v1/categories

Categories. Every category that has PFPs, with how many.

curl -H "Authorization: Bearer pfpvault-YOUR_KEY" \
  "https://pfpvault.net/api/v1/categories"
{ "items": [ { "name": "Animals", "slug": "animals", "description": "Cats, dogs, birds and wildlife.", "count": 412, "url": "https://pfpvault.net/?category=animals", "cover": "https://pfpvault.net/api/media/…" } ] }

GET/api/v1/bundles

Bundles. Matching sets: a cover (A) plus the rest. Up to 100, featured first.

curl -H "Authorization: Bearer pfpvault-YOUR_KEY" \
  "https://pfpvault.net/api/v1/bundles"
{ "items": [ { "id": "LKZkTaTCEA", "slug": "matching-bears", "title": "Matching Bears", "description": "", "url": "https://pfpvault.net/bundles/matching-bears", "category": "Cute", "items": 4, "downloads": 52, "cover": "https://pfpvault.net/api/media/…", "created_at": "…" } ] }

GET/api/v1/bundles/{slug}

One bundle. A bundle with all its PFPs in order (first = cover A).

curl -H "Authorization: Bearer pfpvault-YOUR_KEY" \
  "https://pfpvault.net/api/v1/bundles/matching-bears"
{ "bundle": { …bundle… }, "items": [{ …PFP object… }, { …PFP object… }, …] }

GET/api/v1/banners

Banners. Every banner on the Banners page. media_type is video for clips.

curl -H "Authorization: Bearer pfpvault-YOUR_KEY" \
  "https://pfpvault.net/api/v1/banners"
{ "items": [ { "id": "a1b2c3d4", "title": "Night city", "subtitle": "", "image": "https://pfpvault.net/api/media/….gif", "mime": "image/gif", "media_type": "image", "downloads": 9, "url": "https://pfpvault.net/banners", "created_at": "…" } ] }

GET/api/v1/users/{username}

Public profile. A member's public profile and their 6 newest approved PFPs.

curl -H "Authorization: Bearer pfpvault-YOUR_KEY" \
  "https://pfpvault.net/api/v1/users/vaultfan"
{ "user": { "username": "vaultfan", "name": "Vault Fan", "bio": "", "role": "user", "joined": "…", "url": "https://pfpvault.net/u/vaultfan", "uploads": 12, "downloads": 340, "likes": 51 }, "recent": [{ …PFP object… }, …] }

GET/api/v1/stats

Site stats. Totals for the whole site.

curl -H "Authorization: Bearer pfpvault-YOUR_KEY" \
  "https://pfpvault.net/api/v1/stats"
{ "pfps": 5210, "downloads": 81234, "users": 930, "bundles": 44, "categories": 12, "added_this_week": 230, "site": "https://pfpvault.net" }

GET/api/v1/key

Your key. Limits and usage of the key you send. Does not count against your limits.

curl -H "Authorization: Bearer pfpvault-YOUR_KEY" \
  "https://pfpvault.net/api/v1/key"
{ "key": { "name": "My bot", "prefix": "pfpvault-AbCd", "status": "active", "rate_limit_per_minute": 600, "daily_limit": null, "requests_today": 312, "requests_total": 10450, "last_used_at": "…", "created_at": "…" } }

Code examples

JavaScript / Node.js (18+)

const res = await fetch("https://pfpvault.net/api/v1/random?category=animals", {
  headers: { Authorization: `Bearer ${process.env.PFPVAULT_KEY}` },
});
if (!res.ok) throw new Error((await res.json()).message);
const { pfp } = await res.json();
console.log(pfp.title, pfp.image);

Python

import os, requests
r = requests.get("https://pfpvault.net/api/v1/search",
    params={"q": "cat", "limit": 5},
    headers={"Authorization": f"Bearer {os.environ['PFPVAULT_KEY']}"})
r.raise_for_status()
for p in r.json()["items"]:
    print(p["title"], p["url"])

discord.js: a /pfp command

const { SlashCommandBuilder, EmbedBuilder } = require("discord.js");
module.exports = {
  data: new SlashCommandBuilder().setName("pfp").setDescription("A random PFP from PFP Vault"),
  async execute(interaction) {
    const r = await fetch("https://pfpvault.net/api/v1/random?anim=hide", { headers: { Authorization: `Bearer ${process.env.PFPVAULT_KEY}` } });
    const { pfp } = await r.json();
    const embed = new EmbedBuilder().setTitle(pfp.title).setURL(pfp.url).setImage(pfp.image)
      .setFooter({ text: pfp.credit ? `Photo by ${pfp.credit.creator} · ${pfp.credit.license}` : "PFP Vault" });
    await interaction.reply({ embeds: [embed] });
  },
};

Handling the rate limit

async function get(url) {
  const res = await fetch(url, { headers: { Authorization: `Bearer ${KEY}` } });
  if (res.status === 429) {
    const wait = Number(res.headers.get("retry-after") || 60);
    await new Promise((r) => setTimeout(r, wait * 1000));
    return get(url);
  }
  return res.json();
}

Good to know

  • Cache lists that rarely change (categories, bundles, stats) for a few minutes instead of asking every time.
  • Only approved PFPs are returned. Removed PFPs give 404, so handle that.
  • Downloading files through the API does not need a login; please link back to the PFP page (url) where you can.
  • IDs never change; titles and tags can.
  • The API version is in the path (/v1). New fields can be added at any time; existing fields keep their meaning.

API terms

  1. Your key is personal. Do not share it or publish it.
  2. Stay within your limits. Do not try to get around them with extra keys or IP addresses.
  3. Do not copy PFP Vault as a whole or build a competing gallery with its content.
  4. Show the creator and license for PFPs that have a credit, and do not claim PFPs as your own.
  5. Respect the Terms of Service and guidelines. No use for spam, harassment or anything illegal.
  6. PFP Vault can change limits, turn off keys that break these terms, or change the API, and tries to announce big changes in advance.