What the lookup returns
The player image service takes one required parameter, playerId, a positive numeric player identifier sent as a string. On success it returns JSON in the standard envelope, like every service under /api/v1/football. The data object holds id, the player identifier you requested, and image, the image payload as supplied. The meta object records the feed, the UTC fetch time, the cache time and the qualification state.
The image field is not an immediately usable public URL and should never be treated as one. It is a payload your server reads, decodes according to the encoding you see in a saved sample, validates as a real image and then serves from your own address. On failure, the service returns the gateway's structured JSON error with a code, an error message and a requestId.
- Success: JSON with data.id, data.image and meta.
- Decode and validate the payload on your server.
- Failure: JSON error; 404 image_unavailable means no portrait.
Many players will not have a portrait
Portrait coverage is uneven. Established players in major competitions are more likely to have one; young players, recent signings and players in lower divisions often do not. For those, the lookup returns a 404 with the image_unavailable code. A product that assumes every player has a photo will be full of broken image icons.
Design every portrait slot with a fallback from the start. A neutral circle with the player's initials, derived from the supplied name, is clear and honest. Do not use a stock photo that could be mistaken for the real person, and never substitute a portrait of a different player who shares a name.
Looking up a portrait in Python
The example is a server-side function that performs the lookup, parses the JSON, decodes and validates the payload, and caches either the image or the absence. It never writes the JSON response to disk as an image. The decoding helper is written from your saved sample, and the type check inspects the decoded bytes to choose the content type and extension.
import os, requests
def fetch_portrait(player_id: str):
r = requests.get(
"https://footballapi.net/api/v1/football/player-images",
params={"playerId": player_id},
headers={"X-API-Key": os.environ["FOOTBALLAPI_KEY"], "Accept": "application/json"},
timeout=20,
)
body = r.json() # success and error bodies are JSON
if not r.ok:
if body.get("code") == "image_unavailable":
cache_absence(player_id)
return None
log_error(body.get("code"), body.get("requestId"))
raise RuntimeError(body.get("code"))
raw = decode_image_payload(body["data"]["image"]) # from your saved sample
kind = detect_raster_type(raw) # inspect magic bytes: png, jpeg, webp
if kind is None:
raise ValueError("payload is not a raster image")
cache_image(player_id, raw, kind) # extension and content type from kind
return raw, kindValidation rules worth enforcing
Decoded payloads deserve the same caution as any uploaded file. Check the leading bytes to confirm a raster format you accept, reject anything else, and set a sensible size ceiling. Choose the stored extension and served content type from the detected format rather than assuming one. If validation fails, log the player identifier and the fetch time, fall back to initials, and do not keep retrying the same record on every page view.
Keep the key private throughout. Lookups happen on your server or in your build; the browser only ever requests images from your own URLs.
Images of real people
A portrait is an image of a real person. Show portraits in the context of football information: player pages, squad lists, leaderboards and line-ups. Do not crop, filter or edit them in ways that change how the person looks, do not combine them into memes or composites, and do not use them in advertising that implies endorsement.
Young players warrant particular care. If a player, club or representative asks you to remove a portrait, remove it promptly and contact support if you think the source image is wrong.
Alt text and layout
Where the player's name appears next to the portrait, give the image empty alt text to avoid repetition for screen reader users. Where it stands alone, use the supplied player name. Avoid describing physical appearance; the purpose is identification.
Give every portrait explicit dimensions so pages do not jump as images load, and resize once on your server for the sizes your design uses. Lazy-load portraits below the fold so long squad pages appear quickly.
Caching images and absences
Cache validated portraits by player identifier for a week or two. Cache image_unavailable results too, for several days, so a squad full of young players does not trigger a burst of lookups on every view; re-check absences on a slow cycle or when the player appears in the updated-players feed. Never cache an entitlement error or an upstream 502 or 504 as an absence, because those are temporary or fixable conditions rather than facts about the player.
Always key by identifier rather than name, and take each portrait from the identifier in the same row you render, so a sorting bug cannot attach one player's face to another's statistics.
Build-time portraits for static sites
Pre-rendered pages can perform lookups during the build. The script requests each player's JSON record, decodes and validates the payload, writes the image named by player identifier with an extension from the detected type, and records absences so templates render the fallback. Many products combine this with a cached server route for dynamic pages such as live leaderboards.
Keep the portrait asset manifest separate from the player biography records. For each generated image, record the player identifier, detected format, chosen dimensions, local output path and time of the last successful lookup. A biography change does not necessarily mean that a portrait changed, and the absence of a new portrait must not erase a valid previous image without a deliberate editorial decision. Use that manifest to update templates predictably when the same player appears in a squad, a leaderboard and a profile page. All three views should refer to the same identity, rather than generating unrelated files from slightly different spellings of the player's name.
Generate a new asset into a temporary output location, validate it, then replace the previous file atomically. This prevents readers seeing half-written image files during a rebuild. If your asset filenames contain a content hash, update the HTML reference only after the new file is available. Retain the previous working image when decoding fails, but record the failure and avoid presenting that retained asset as freshly retrieved. A successful request and a successful image conversion are separate events in your pipeline, so monitor both. Never publish the original JSON sample, build logs containing credentials, or the API request headers alongside the generated assets. Only the intended, validated display image belongs in the public output.
Plan scope
Player images are a global service that is not included in Starter. It requires Growth at EUR 99 a month or Pro at EUR 249 and activation for your account. Access supports display alongside football data; it is not a general licence for the images, and our data licensing page describes the boundaries.
Each successful lookup counts towards the daily inclusion of 25,000 on Growth or 250,000 on Pro. With caching, lookups are far fewer than page views. Above the inclusion, requests are EUR 0.002 each on the monthly EUR invoice; failed and denied requests, including image_unavailable responses, are not billed.
Questions developers ask
- What does a successful player-images response contain?
- JSON in the standard envelope: data.id with the player identifier, data.image with the supplied image payload, and meta.
- Can I use data.image directly in the browser?
- No. It is not a usable public URL. Decode and validate it on your server and serve the image from your own URL, keeping the key private.
- What happens when a player has no portrait?
- The service returns a 404 JSON error with the image_unavailable code. Show an initials fallback and cache the absence.
- Is the player image service on Starter?
- No. It requires Growth or Pro and separate service activation.
- Can I use portraits in advertising?
- No. Access supports display alongside football data, not endorsement-style or promotional use.
Guides and documentation
Check the plan before you write the integration.
Starter EUR 29, Growth EUR 99, Pro EUR 249 per month. No free trial. No VAT currently charged.