PixHub API
The PixHub API lets you upload, list, inspect, and delete files programmatically, and check usage statistics for your account. It's a small JSON/REST API — no SDK required, just an HTTP client and an API key.
- Base URL:
https://pixhub.ro/api - Format: JSON in, JSON out (uploads are
multipart/form-data) - Auth: an API key on every request
Getting an API key
- Sign in at pixhub.ro.
- Go to your account's API Key page (
/cont/cheie-api). - Click Generate API Key and confirm with your password.
- Copy the key immediately — it's shown in full only once. Regenerating it invalidates the old key immediately.
Authentication
Send your key one of two ways:
X-Api-Key: YOUR_API_KEY
GET https://pixhub.ro/api/stats?api_key=YOUR_API_KEY
The header is checked first; the api_key query parameter is used only if the header
is absent. Treat your key like a password — anyone with it can act as your account. Every
request, successful or not, is logged (endpoint, method, response code, IP, timestamp) and
visible in your usage chart on the API Key page.
Optionally, an account can be restricted to a specific IP allowlist (IPs or CIDR ranges),
configured on the same page — requests from any other IP get a 403.
Endpoints
POST /api/upload — Upload a file
Uploads a single file for the authenticated user. Costs 10 rate-limit units (see Rate limits below).
multipart/form-data with one field:
| Field | Required | Description |
|---|---|---|
file | yes | The file to upload. |
Accepted types: JPEG, PNG, GIF, WebP images; MP4, WebM, QuickTime (MOV) videos.
Max size: 200 MB by default (server-configurable).
Response — 201 Created:
{
"upload": {
"slug": "aB3xY9",
"title": null,
"url": "https://pixhub.ro/i/aB3xY9",
"direct_url": "https://pixhub.ro/i/aB3xY9.jpg",
"thumb_url": "https://pixhub.ro/t/aB3xY9",
"mime_type": "image/jpeg",
"size": 245678,
"views": 0,
"likes": 0,
"created_at": "2026-07-20T10:32:00+00:00"
}
}
GET /api/uploads — List your uploads
Returns a paginated list of the authenticated user's uploads, newest first, 50 per page.
| Parameter | In | Required | Description |
|---|---|---|---|
page | query | no | Page number, defaults to 1. |
Response — 200 OK (Laravel's standard paginator shape):
{
"current_page": 1,
"data": [ { "slug": "aB3xY9", "...": "..." } ],
"first_page_url": "https://pixhub.ro/api/uploads?page=1",
"from": 1,
"last_page": 1,
"last_page_url": "https://pixhub.ro/api/uploads?page=1",
"links": [ { "url": null, "label": "« Previous", "active": false } ],
"next_page_url": null,
"path": "https://pixhub.ro/api/uploads",
"per_page": 50,
"prev_page_url": null,
"to": 1,
"total": 1
}
GET /api/uploads/{slug} — Get a single upload
Returns details for one of the authenticated user's uploads.
Response — 200 OK:
{ "upload": { "slug": "aB3xY9", "...": "..." } }
404 if no upload with that slug exists for your account (see Errors below — 404
responses have a different body shape than most other errors).
DELETE /api/uploads/{slug} — Delete an upload
Permanently deletes one of the authenticated user's uploads, including its stored file and thumbnail. Costs 3 rate-limit units. This cannot be undone.
Response — 200 OK:
{ "deleted": true }
GET /api/stats — Account usage statistics
{
"uploads": 42,
"albums": 5,
"total_views": 1337,
"api_requests": 980
}
| Field | Description |
|---|---|
uploads | Total uploads on the account. |
albums | Total albums on the account. |
total_views | Sum of feed views and direct file views across all uploads. |
api_requests | Total API requests logged for this account so far — does not include the current in-flight request. |
The upload object
| Field | Type | Description |
|---|---|---|
slug | string | Unique identifier for the file. |
title | string | null | User-set title. |
url | string | The file's page on PixHub. |
direct_url | string | Direct link to the raw file. |
thumb_url | string | null | Thumbnail link — null if no thumbnail has been generated yet. |
mime_type | string | MIME type of the file. |
size | integer | File size in bytes. |
views | integer | Total views (page + direct + embed). |
likes | integer | Number of likes. |
created_at | string | ISO 8601 upload timestamp. |
Rate limits
Every account has independent per-minute, per-hour, and per-day quotas, plus a short burst window — enforced per API key, not per IP.
| Window | Default limit |
|---|---|
| Per minute | 60 units |
| Per hour | 1,000 units |
| Per day | 10,000 units |
| Burst (10 seconds) | 20 units |
Most requests cost 1 unit. Two cost more:
| Endpoint | Cost |
|---|---|
POST /api/upload | 10 units |
DELETE /api/uploads/{slug} | 3 units |
| Everything else | 1 unit |
Every successful response includes:
X-RateLimit-Limit: 60
X-RateLimit-Remaining: 55
X-RateLimit-Reset: 1784540916
X-RateLimit-Cost: 1
As usage approaches 80% of the per-minute quota, responses may be delayed slightly (up to
2 seconds) as a soft warning before a hard 429. Read X-RateLimit-Remaining and back off
before it hits 0, and honor Retry-After on 429/503 responses.
| Status | Body | When |
|---|---|---|
| 429 | {"error": "Burst limit exceeded. Slow down."} | More than 20 units in 10 seconds. |
| 429 | {"error": "Rate limit exceeded (per minute)."} | Per-minute quota exceeded. |
| 429 | {"error": "Rate limit exceeded (per hour)."} | Per-hour quota exceeded. |
| 429 | {"error": "Rate limit exceeded (per day)."} | Per-day quota exceeded. |
| 403 | {"error": "Access suspended by administrator."} (or a custom reason) | API-specific suspension, not a quota. |
| 503 | {"error": "API temporarily disabled."} | The whole API is temporarily disabled server-side. |
Errors
The API uses two different JSON shapes, depending on where the error originates — check the status code before assuming which shape you got.
Standard errors — authentication, authorization, and rate-limit failures:
{ "error": "Invalid API key." }
| Status | Meaning |
|---|---|
| 401 | Missing or invalid API key. |
| 403 | Account suspended, IP not on the allowlist, or otherwise forbidden. |
| 429 | Rate limit exceeded. |
| 503 | API temporarily disabled server-side. |
404 Not Found uses Laravel's default exception shape instead — note there's no error
key here, and the message can include the internal Eloquent model name:
{ "message": "No query results for model [App\\Models\\Upload]." }
422 Unprocessable Entity (validation failures on POST /api/upload) also uses Laravel's
default shape. Messages are always in English, regardless of the site's default locale —
the API forces an English locale on every request:
{
"message": "The file field is required.",
"errors": { "file": ["The file field is required."] }
}
Examples in common languages
The examples below cover the two most common operations — uploading a file and listing your
uploads. Replace YOUR_API_KEY with your real key.
curl
# Upload a file
curl -H "X-Api-Key: YOUR_API_KEY" \
-F "file=@/path/to/photo.jpg" \
https://pixhub.ro/api/upload
# List uploads
curl -H "X-Api-Key: YOUR_API_KEY" "https://pixhub.ro/api/uploads?page=1"
# Delete an upload
curl -X DELETE -H "X-Api-Key: YOUR_API_KEY" https://pixhub.ro/api/uploads/aB3xY9
JavaScript (fetch)
const API_KEY = 'YOUR_API_KEY';
// Upload a file (browser <input type="file"> or Node with a Blob/File)
async function uploadFile(file) {
const form = new FormData();
form.append('file', file);
const res = await fetch('https://pixhub.ro/api/upload', {
method: 'POST',
headers: { 'X-Api-Key': API_KEY },
body: form,
});
if (!res.ok) throw new Error((await res.json()).error ?? (await res.json()).message);
const { upload } = await res.json();
return upload;
}
// List uploads
async function listUploads(page = 1) {
const res = await fetch(`https://pixhub.ro/api/uploads?page=${page}`, {
headers: { 'X-Api-Key': API_KEY },
});
return res.json();
}
Python (requests)
import requests
API_KEY = "YOUR_API_KEY"
HEADERS = {"X-Api-Key": API_KEY}
# Upload a file
with open("/path/to/photo.jpg", "rb") as f:
res = requests.post(
"https://pixhub.ro/api/upload",
headers=HEADERS,
files={"file": f},
)
res.raise_for_status()
upload = res.json()["upload"]
# List uploads
res = requests.get(
"https://pixhub.ro/api/uploads",
headers=HEADERS,
params={"page": 1},
)
uploads = res.json()
PHP (curl)
<?php
$apiKey = 'YOUR_API_KEY';
// Upload a file
$curl = curl_init('https://pixhub.ro/api/upload');
curl_setopt_array($curl, [
CURLOPT_POST => true,
CURLOPT_HTTPHEADER => ["X-Api-Key: $apiKey"],
CURLOPT_POSTFIELDS => ['file' => new CURLFile('/path/to/photo.jpg')],
CURLOPT_RETURNTRANSFER => true,
]);
$upload = json_decode(curl_exec($curl), true)['upload'];
curl_close($curl);
// List uploads
$curl = curl_init('https://pixhub.ro/api/uploads?page=1');
curl_setopt_array($curl, [
CURLOPT_HTTPHEADER => ["X-Api-Key: $apiKey"],
CURLOPT_RETURNTRANSFER => true,
]);
$uploads = json_decode(curl_exec($curl), true);
curl_close($curl);
Go (net/http)
package main
import (
"bytes"
"encoding/json"
"io"
"mime/multipart"
"net/http"
"os"
)
const apiKey = "YOUR_API_KEY"
func uploadFile(path string) (map[string]any, error) {
file, err := os.Open(path)
if err != nil {
return nil, err
}
defer file.Close()
body := &bytes.Buffer{}
writer := multipart.NewWriter(body)
part, _ := writer.CreateFormFile("file", path)
io.Copy(part, file)
writer.Close()
req, _ := http.NewRequest("POST", "https://pixhub.ro/api/upload", body)
req.Header.Set("X-Api-Key", apiKey)
req.Header.Set("Content-Type", writer.FormDataContentType())
res, err := http.DefaultClient.Do(req)
if err != nil {
return nil, err
}
defer res.Body.Close()
var result map[string]any
json.NewDecoder(res.Body).Decode(&result)
return result, nil
}
func listUploads() (map[string]any, error) {
req, _ := http.NewRequest("GET", "https://pixhub.ro/api/uploads?page=1", nil)
req.Header.Set("X-Api-Key", apiKey)
res, err := http.DefaultClient.Do(req)
if err != nil {
return nil, err
}
defer res.Body.Close()
var result map[string]any
json.NewDecoder(res.Body).Decode(&result)
return result, nil
}