PHero AI API reference
Tag any image or video with one request.
Send prepared media to the endpoint below and get structured JSON back: a description, confidence-scored tags, safety signals, and — for video — a timestamped timeline.
Endpoint
One focused endpoint.
POST https://test.phero-ai.com/api/phero/v1/tag
POST /api/phero/tag powers the free on-site playground only. Billable integrations always use /api/phero/v1/tag with an API key.
Authentication
Bearer keys from your workspace.
Create keys in the API workspace. Each key is shown once — store it immediately. You can hold up to 10 keys and revoke any of them at any time; revoked keys return 401.
Authorization: Bearer phk_YOUR_KEY
Request
Multipart fields.
POST multipart/form-data. Prepare frames as JPEG, PNG, or WebP: at most 3 MB each, 8 MB of frames per request, 10 MB total.
Form fields
- mediaType
- Required
- image or video.
- frame
- Required
- One image frame per part: exactly 1 for images, 1–3 for video. JPEG, PNG, or WebP; at most 3 MB each and 8 MB total.
- frameTimestamps
- Video only
- JSON array of seconds, one entry per frame part (e.g. [0, 4.5, 9]). Must match the frame count.
- durationSeconds
- Optional
- Total video length in seconds. Billable seconds are the larger of this value and the last frame timestamp, rounded up.
Headers
- Authorization
- Required
- Bearer phk_ followed by your 48-character hex API key, created in the API workspace. Shown once — store it immediately.
- Idempotency-Key
- Optional
- Up to 200 characters. Retrying with the same key within the hold window never charges twice.
Preparing media
Downscale frames to JPEG before sending — up to 1600px on the long edge for images, 1280px for video frames, quality around 0.84. The server normalizes uploads the same way, so matching it client-side keeps requests small without changing results. Media is processed transiently and deleted after the response.
Response
Predictable JSON.
- description
- Always
- One factual sentence describing the media (up to 800 characters).
- tags
- Always
- 1–15 objects with a lowercase label (up to 80 characters) and a confidence score from 0 to 1.
- safety
- Always
- Content-risk signals from 0 to 1: adult and violence. Built for routing and human moderation support.
- timeline
- Video only
- Up to 8 segments with numeric startSeconds, endSeconds, and a labels array.
- unitsCharged
- Always
- Credits consumed by this request: 1 per image, 1 per analyzed video-second rounded up.
- balance
- Always
- Remaining credit balance after this request.
Example
{
"description": "A cyclist riding beside a lake at sunrise.",
"tags": [
{ "label": "cyclist", "confidence": 0.98 },
{ "label": "lake", "confidence": 0.96 },
{ "label": "sunrise", "confidence": 0.91 }
],
"safety": { "adult": 0.01, "violence": 0.02 }
}Billing
Pay per analyzed unit.
One credit tags one image. Video costs one credit per analyzed second, rounded up, using the larger of the declared duration and the last frame timestamp (minimum 1, capped at one hour). Every response reports unitsCharged and your remaining balance; a short balance returns 402 with both numbers so you can top up the exact gap. See pricing for plans and packs.
Errors
Status codes.
Failures return JSON with an error message and never cache. Retry 502s and 504s with the same idempotency key.
- 400
- Invalid upload: bad media type, frame count, or frame timestamps.
- 401
- Missing, malformed, or revoked API key.
- 402
- Insufficient credits. The response includes your balance and the required units.
- 413
- Prepared media too large: over 10 MB per request, 3 MB per frame, or 8 MB of frames.
- 415
- Frames must be JPEG, PNG, or WebP images that decode cleanly.
- 429
- Rate limited: 60 requests per minute per API key.
- 502
- Tagging failed upstream. Safe to retry (reuse your idempotency key).
- 503
- Tagging or usage tracking is temporarily unavailable.
- 504
- Tagging took too long. Please try again.
Examples
Copy, paste, tag.
Replace phk_YOUR_KEY with a workspace key. Video runs allow up to 3 minutes — set client timeouts accordingly.
curl
Image
curl https://test.phero-ai.com/api/phero/v1/tag \ -H "Authorization: Bearer phk_YOUR_KEY" \ -F "mediaType=image" \ -F "[email protected]"
Video
curl https://test.phero-ai.com/api/phero/v1/tag \ -H "Authorization: Bearer phk_YOUR_KEY" \ -H "Idempotency-Key: clip-001" \ -F "mediaType=video" \ -F "[email protected]" -F "[email protected]" -F "[email protected]" \ -F "frameTimestamps=[0, 5, 10]" \ -F "durationSeconds=12"
Python
Image
import requests
response = requests.post(
"https://test.phero-ai.com/api/phero/v1/tag",
headers={"Authorization": "Bearer phk_YOUR_KEY"},
files={"frame": open("photo.jpg", "rb")},
data={"mediaType": "image"},
timeout=120,
)
response.raise_for_status()
print(response.json()["description"])Video
import requests
frames = [("frame", open(f"frame-{t}.jpg", "rb")) for t in (0, 5, 10)]
response = requests.post(
"https://test.phero-ai.com/api/phero/v1/tag",
headers={"Authorization": "Bearer phk_YOUR_KEY", "Idempotency-Key": "clip-001"},
files=frames,
data={"mediaType": "video", "frameTimestamps": "[0, 5, 10]", "durationSeconds": "12"},
timeout=180,
)
response.raise_for_status()
body = response.json()
print(body["timeline"], body["unitsCharged"], body["balance"])JavaScript
Image
const form = new FormData();
form.append("mediaType", "image");
form.append("frame", photoBlob, "photo.jpg");
const response = await fetch("https://test.phero-ai.com/api/phero/v1/tag", {
method: "POST",
headers: { Authorization: "Bearer phk_YOUR_KEY" },
body: form,
});
if (!response.ok) throw new Error(await response.text());
const { description } = await response.json();Video
const form = new FormData();
form.append("mediaType", "video");
for (const [blob, name] of frameBlobs) form.append("frame", blob, name);
form.append("frameTimestamps", JSON.stringify([0, 5, 10]));
form.append("durationSeconds", "12");
const response = await fetch("https://test.phero-ai.com/api/phero/v1/tag", {
method: "POST",
headers: { Authorization: "Bearer phk_YOUR_KEY", "Idempotency-Key": "clip-001" },
body: form,
});
if (!response.ok) throw new Error(await response.text());
const { timeline, unitsCharged, balance } = await response.json();Try it before you integrate.
Run tagging in the browser playground, then grab a key in the workspace.