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
- Log in and open Account → API.
- Fill in the short form: what you are building, roughly how many requests you expect and which data you need.
- 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
429with aRetry-Afterheader (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." }| Status | error | Meaning |
|---|---|---|
| 400 | bad_request | A parameter is wrong. |
| 401 | api_key_required / invalid_key | No key sent while one is required, or the key does not exist. |
| 403 | key_revoked | The key was turned off by PFP Vault. |
| 404 | not_found | That PFP, bundle or user does not exist (or is not public). |
| 429 | rate_limited / daily_limit | Too 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
}imageis the full file,urlthe page on PFP Vault.animatedis true for GIFs, animated WebP and clips.media_typeisvideofor MP4/WEBM clips: show those with a video player (Discord embeds cannot play them, link tourlinstead).creditis 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.
| Parameter | Value | What it does |
|---|---|---|
| category | string | Category slug, e.g. animals (see /categories) |
| q | string | Only PFPs matching this search term |
| ai | hide | only | Leave out or only show AI-made PFPs |
| anim | only | hide | Only 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 }GET/api/v1/search
Search. Search and filter PFPs, like the homepage. An exact PFP ID as q returns just that PFP.
| Parameter | Value | What it does |
|---|---|---|
| q | string | Search words (title, tags, category, color) or an exact ID |
| category | string | Category slug |
| color | string | Blue, Purple, Pink, Orange, Yellow, Green, Teal, Cyan, Black, Neon |
| ai | hide | only | AI filter |
| anim | only | hide | Only animated, or only still images |
| sort | trending | new | popular | liked | random | Default trending |
| limit | 1–50 | Default 25 |
| offset | number | Skip 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
- Your key is personal. Do not share it or publish it.
- Stay within your limits. Do not try to get around them with extra keys or IP addresses.
- Do not copy PFP Vault as a whole or build a competing gallery with its content.
- Show the creator and license for PFPs that have a
credit, and do not claim PFPs as your own. - Respect the Terms of Service and guidelines. No use for spam, harassment or anything illegal.
- PFP Vault can change limits, turn off keys that break these terms, or change the API, and tries to announce big changes in advance.
