PixHub
PixHub API

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

  1. Sign in at pixhub.ro.
  2. Go to your account's API Key page (/cont/cheie-api).
  3. Click Generate API Key and confirm with your password.
  4. 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:

FieldRequiredDescription
fileyesThe 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.

ParameterInRequiredDescription
pagequerynoPage 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
}
FieldDescription
uploadsTotal uploads on the account.
albumsTotal albums on the account.
total_viewsSum of feed views and direct file views across all uploads.
api_requestsTotal API requests logged for this account so far — does not include the current in-flight request.

The upload object

FieldTypeDescription
slugstringUnique identifier for the file.
titlestring | nullUser-set title.
urlstringThe file's page on PixHub.
direct_urlstringDirect link to the raw file.
thumb_urlstring | nullThumbnail link — null if no thumbnail has been generated yet.
mime_typestringMIME type of the file.
sizeintegerFile size in bytes.
viewsintegerTotal views (page + direct + embed).
likesintegerNumber of likes.
created_atstringISO 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.

WindowDefault limit
Per minute60 units
Per hour1,000 units
Per day10,000 units
Burst (10 seconds)20 units

Most requests cost 1 unit. Two cost more:

EndpointCost
POST /api/upload10 units
DELETE /api/uploads/{slug}3 units
Everything else1 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.

StatusBodyWhen
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." }
StatusMeaning
401Missing or invalid API key.
403Account suspended, IP not on the allowlist, or otherwise forbidden.
429Rate limit exceeded.
503API 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
}