UPDATE 2025-11-06: I'm making this public for historical interest, but a lot of the information here is now outdated. For example, you can now change display names and the settings have probably been changed following updates to how birthdates are handled.
This is an attempt to document how Fur Affinity works internally. This document might contain errors or be outdated by the time you are reading it, so please do not fully trust this document.
Fur Affinity does not have an official API, and it is not clear if they will add one. Almost every action of the site occurs via a GET or url-encoded POST request, including even things that would be better suited to an API, like managing folders.
The site is based on a custom PHP codebase originating from early 2005. After having issues in August of that year, it was (accoding to journals from the time) refactored and later relauched on 10 December 2005. The site's codebase has stayed largely the same, until an alleged refactor that started in 2021 or 2022 (source: discord messages from dragoneer on the FA discord).
Despite that there are never really situtations where a POST request would not include some given parameter and thus could throw an error, FA often does often not require that parameters are given and will set default values, including sometimes nontrivial ones.
This section includes any methods that get content. These largely do not change server side state; however, they might chanage state in some cases - for example, the view counter on submissions.
Retrieve the front page HTML, which lists recent submissions.
Retrieve the browse page HTML with default settings. Browse is essentially a variant of the front page that allows filtering.
Retrieve the browse page HTML with the specified filters.
| Name | Type | Purpose | Details | Required |
|---|---|---|---|---|
cat |
Integer | Category ID | ||
atype |
Integer | Art type ID | ||
species |
Integer | Species ID | ||
gender |
Integer | Geneder ID | ||
perpage |
Integer | Number of items displayed per page | ||
page |
Integer | Page number to load | ||
go |
String | None | Not actually used, sent as a conseqence of giving the form button a name attribute. |
POST /browse HTTP/1.1
...
cat={category}&atype={type}&species={species}&gender={gender}&perpage={perpage}&page={page_number}&go=Apply
Not including a parameter seems to set it to 1, which coresponds to "All"/"don't apply the filter".
The go parameter is not needed and is only included becuase the form gives the button that submits it (the "Apply" button; value="Apply") a name attribute (name="go").
The integer IDs for the categories are lagrely in the same order that they appear in the dropdown, but categories added after the site's creation are often out of order, most likely since the IDs are not updateable.
TODO: What does
perpagedefault to? The account default or a static value of 48?
POST /browse HTTP/1.1
...
cat=1&atype=1&species=1&gender=0&perpage=48&page=1&go=Apply
Get the help page HTML.
Get the advertising info page HTML.
Get the Black Lives Matter info page HTML.
Get the terms of service page HTML.
Get the prviacy policy page HTML.
Get the code of conduct page HTML.
Get the upload policy page HTML.
Get the code of conduct page HTML.
Get the page contents for the submission with the given id.
- The submission has the
idset tosubmissionImg.
State setting routes create, update or delete something server-side in a way the user would not have to think about to answer the question "Does this change something server side?".
For organisation, any route that might be a "state getting route" but is directly tied to a setting route is placed under the setting routes.
Get the account settings form page.
Saves account settings.
| Name | Type | Purpose | Details | Required |
|---|---|---|---|---|
do |
String | Unknown, probably action to preform | ||
fa_useremail |
String | Account email address | ✓ | |
bdaymonth |
Integer | Month index of the user's birthday (1 - 12) | ||
bdayday |
Integer | Day of the month of the user's birthday | ||
bdayyear |
Integer | Year of the user's birthday | Falls back to current year if not included | |
viewmature |
Integer | Maxium maturity level of artwork in NSFW mode | 0 = General, 2 = Mature, 1 = Adult | |
timezone |
Integer | Your preferred timezone | Format for dates is SHHMM from UTC: S is sign (+/-), HH is hours and MM is minutes |
|
timezone_dst |
Boolean | If daylight savings time corrections should be applied | Excluding means disabling DST | |
fullview |
Integer | Weather to display full artwork or 400px previews on submission pages | ||
style |
Integer | Which set of HTML templates to use | ||
stylesheet |
Integer | Which CSS stylesheet to use | ||
scales_enabled |
Integer | Enables the Shinies (dontations via paypal) | 0 = Disabled, 1 = Enabled | |
paypal_email |
Integer | Paypal email for the Shinies | ||
display_mode |
Integer | Show or hide the donation feed | 0 = Show feed, 1 = Hide feed | |
scales_message_enabled |
Integer | Allow users to leave messages with shinies if enabled | 0 = Disabled, 1 = Enabled | |
scales_name |
String | Singular name of shinies | ||
scales_plural_name |
String | Plural name of scales | ||
scales_cost |
Integer | The cost of a single shiney | ||
account_disabled |
Integer | If your account is disabled or not | 0 = Enabled, 1 = Disabled | |
newpassword |
Integer | First box to enter new password | ||
newpassword2 |
Integer | Second box to enter new password | ||
oldpassword |
Integer | Old password | ✓ |
do=update&fa_useremail=lmao%40fucked.com&bdaymonth=1&bdayday=1&bdayyear=1995&viewmature=0&timezone=%2B0000&fullview=1&style=beta&stylesheet=ui_theme_dark&scales_enabled=0&paypal_email=&display_mode=0&scales_message_enabled=1&account_disabled=0&newpassword=&newpassword2=&oldpassword=password1234567890
- User names are not stored with underscores (
_) nor are they case sensitive and are only for display purposes. The name without underscores and optionally only in lowercase is considered the artist's "lower". - Users have different real usernames ("lowers") and display names, but you cannot seem to update the display name.
- Submission IDs are incremental, starting at 1 and continuing to the current submission (51946515 as of writing).
- The submission image URL uses the following format:
https://d.furaffinity.net/art/{lower}/{timestamp1}/{timestamp2}.{lower}_{filename}
lower: The artist's lower.timestamp1: UNIX timestamp. This may be the creation time of the submission.timestamp2: UNIX timestamp. This may be the creation time of the file.filename: The original name of the file.Examples:
https://d.furaffinity.net/art/jamesmiller/1631299361/1631299361.jamesmiller_20210910_154113.jpghttps://d.furaffinity.net/art/knot126/1679085789/1679085708.knot126_dragonised_v12.pnghttps://d.furaffinity.net/art/zell950/1682831642/1682831642.zell950_6.png
| Subdomain | Purpose |
|---|---|
a.furaffinity.net |
Avatars/Profile images |
d.furaffinity.net |
Image storage |
t.furaffinity.net |
Thumnail image storage |
rv.furaffinity.net |
FA community ads |
There is a fox at the bottom of every page. It is a reference to a cow in a comment on an Amazon service's pages.
-
|\ /|
/_^ ^_\
\v/
The fox goes "moo!"
-
- The original early 2005 codebase may have treated journals and font page news differently instead of using jornals for news. After the December 2005 relaunch, old news cotent was not migrated to journals and as such lost.