Overview of the Full Mediaviz Curation Process
A complete guide to the MediaViz API — from creating your account to viewing AI-powered photo curation results.
Initialize SDK Client
The MediaViz API uses OAuth2 for authentication. Before you can initialize the SDK or make any API calls, you need an OAuth client ID — and, optionally, a client secret — issued by MediaViz.
Don't have a client ID or secret yet? Reach out to MediaViz and we'll generate credentials for you.
First-party vs. third-party clients. MediaViz issues a client secret only to first-party (confidential) clients — your own backend, running on infrastructure you control. With a secret, the SDK authenticates as itself using the client-credentials grant and no user login is involved. A third-party (public) client gets a client ID only: it cannot hold a secret, so the SDK sends each user through the OAuth authorization-code flow with PKCE, and every call acts on behalf of the user who signed in.
The SDK needs both host URLs: MEDIAVIZ_BASE_URL for the API and MEDIAVIZ_PHOTO_UPLOAD_URL for photo uploads, which run on a separate host. Both are required.
React Native and Expo are the exception. A mobile app ships its bundle to the device, so it is a public OAuth client: never put a client secret in one. Expo also only exposes variables prefixed EXPO_PUBLIC_ to app code — and that prefix is a statement of fact, not a setting. Authenticate with PKCE instead, which the SDK does for you.
Create an Environment File
# .env
# Both hosts are required — photo uploads run on a separate host.
MEDIAVIZ_BASE_URL=https://api.mediaviz.ai
MEDIAVIZ_PHOTO_UPLOAD_URL=https://upload.mediaviz.ai
MEDIAVIZ_CLIENT_ID=YOUR_CLIENT_ID
# First-party clients only — include this line only if MediaViz issued
# you a client secret. Third-party clients omit it and use PKCE.
MEDIAVIZ_CLIENT_SECRET=YOUR_CLIENT_SECRET
# .env
# Both hosts are required — photo uploads run on a separate host.
MEDIAVIZ_BASE_URL=https://api.mediaviz.ai
MEDIAVIZ_PHOTO_UPLOAD_URL=https://upload.mediaviz.ai
MEDIAVIZ_CLIENT_ID=YOUR_CLIENT_ID
# First-party clients only — include this line only if MediaViz issued
# you a client secret. Third-party clients omit it and use PKCE.
MEDIAVIZ_CLIENT_SECRET=YOUR_CLIENT_SECRET
# .env
# Both hosts are required — photo uploads run on a separate host.
MEDIAVIZ_BASE_URL=https://api.mediaviz.ai
MEDIAVIZ_PHOTO_UPLOAD_URL=https://upload.mediaviz.ai
MEDIAVIZ_CLIENT_ID=YOUR_CLIENT_ID
# First-party clients only — include this line only if MediaViz issued
# you a client secret. Third-party clients omit it and use PKCE.
MEDIAVIZ_CLIENT_SECRET=YOUR_CLIENT_SECRET
# .env — Expo reads EXPO_PUBLIC_* into the app bundle
EXPO_PUBLIC_MEDIAVIZ_BASE_URL=https://api.mediaviz.ai
EXPO_PUBLIC_MEDIAVIZ_PHOTO_UPLOAD_URL=https://upload.mediaviz.ai
EXPO_PUBLIC_MEDIAVIZ_CLIENT_ID=YOUR_CLIENT_ID
EXPO_PUBLIC_MEDIAVIZ_REDIRECT_URI=yourapp://callback
# No client secret here — a mobile app is always a third-party client.
# Anything EXPO_PUBLIC_ is readable in the shipped app, so a mobile
# client authenticates with PKCE instead.
# .env
# Both hosts are required — photo uploads run on a separate host.
MEDIAVIZ_BASE_URL=https://api.mediaviz.ai
MEDIAVIZ_PHOTO_UPLOAD_URL=https://upload.mediaviz.ai
MEDIAVIZ_CLIENT_ID=YOUR_CLIENT_ID
# First-party clients only — include this line only if MediaViz issued
# you a client secret. Third-party clients omit it and use PKCE.
MEDIAVIZ_CLIENT_SECRET=YOUR_CLIENT_SECRET
# .env
# Both hosts are required — photo uploads run on a separate host.
MEDIAVIZ_BASE_URL=https://api.mediaviz.ai
MEDIAVIZ_PHOTO_UPLOAD_URL=https://upload.mediaviz.ai
MEDIAVIZ_CLIENT_ID=YOUR_CLIENT_ID
# First-party clients only — include this line only if MediaViz issued
# you a client secret. Third-party clients omit it and use PKCE.
MEDIAVIZ_CLIENT_SECRET=YOUR_CLIENT_SECRET
Now that your OAuth client is configured, you can initialize the SDK.
Initialize the SDK Client
import { MediaViz } from '@mediaviz/sdk';
const mediaviz = new MediaViz({
baseUrl: 'https://api.mediaviz.ai',
clientId: 'YOUR_CLIENT_ID',
clientSecret: 'YOUR_CLIENT_SECRET',
redirectUri: 'http://localhost:3000/callback',
});
import { MediaViz, MediaVizConfig } from '@mediaviz/sdk';
const config: MediaVizConfig = {
baseUrl: 'https://api.mediaviz.ai',
clientId: 'YOUR_CLIENT_ID',
clientSecret: 'YOUR_CLIENT_SECRET',
redirectUri: 'http://localhost:3000/callback',
};
const mediaviz = new MediaViz(config);
const { MediaViz } = require('@mediaviz/sdk');
const mediaviz = new MediaViz({
baseUrl: 'https://api.mediaviz.ai',
clientId: 'YOUR_CLIENT_ID',
clientSecret: 'YOUR_CLIENT_SECRET',
redirectUri: 'http://localhost:3000/callback',
});
import {
MediaViz, configureCrypto, createCryptoProvider, createSecureTokenStore,
} from '@mediaviz/react-native-sdk';
import { MediaVizProvider } from '@mediaviz/react-native-sdk/react';
import * as Crypto from 'expo-crypto';
import * as SecureStore from 'expo-secure-store';
import { openAuthSessionAsync } from 'expo-web-browser';
// Hermes ships no SubtleCrypto, so PKCE needs a native provider.
configureCrypto(createCryptoProvider(Crypto));
export function App() {
return (
<MediaVizProvider
MediaViz={MediaViz}
config={{
baseUrl: 'https://api.mediaviz.ai',
clientId: 'YOUR_CLIENT_ID',
redirectUri: 'yourapp://callback',
}}
store={createSecureTokenStore(SecureStore)}
openAuthSession={openAuthSessionAsync}
>
<RootNavigator />
</MediaVizProvider>
);
}
from mediaviz_sdk import MediaVizClient
mediaviz = MediaVizClient(
base_url='https://api.mediaviz.ai',
client_id='YOUR_CLIENT_ID',
client_secret='YOUR_CLIENT_SECRET',
redirect_uri='http://localhost:3000/callback',
)
require_once __DIR__ . '/vendor/autoload.php';
use MediaVizSdk\MediaVizClient;
$mediaviz = new MediaVizClient([
'baseUrl' => 'https://api.mediaviz.ai',
'clientId' => 'YOUR_CLIENT_ID',
'clientSecret' => 'YOUR_CLIENT_SECRET',
'redirectUri' => 'http://localhost:3000/callback',
]);
Creating Your Account
Before making any API calls, you need a user account. MediaViz uses a company-based account model — each account belongs to an organization, and one user must be designated as the company owner.
Create the First Company User
To register a new company and its owner, send a request to POST /api/v1/users/new_company. This endpoint creates both the user and the associated company in a single call.
Required fields:
- Name — the user's full name
- Email — a valid email address (used for verification)
- Password — account password
- Company name — the name of your organization
- Company creation token — issued by MediaViz for this email address. Contact us to request one; see User & Company Management for details.
Accounts created this way are automatically assigned the Company Owner role (account_type 3), which grants full administrative access — there is no account-type parameter to set.
A successful response triggers an email verification message to the provided address and returns the new user's id and company_id (both integers). Record both — you will need them for subsequent requests.
Request Sample
const { id, company_id } = await mediaviz.users.createUserAndCompany(
'Jane Smith',
'jane@example.com',
's3cur3P@ss',
null, // companyId — omit to create a new company
null, // profilePicture — optional avatar URL
null, // paymentPlanType — optional billing plan
'Acme Photography',
null, // credits — optional
{
inviteToken: 'YOUR_COMPANY_CREATION_TOKEN'
}
);
import type { UserDisplay } from '@mediaviz/sdk';
const { id, company_id }: UserDisplay = await mediaviz.users.createUserAndCompany(
'Jane Smith',
'jane@example.com',
's3cur3P@ss',
null, // companyId — omit to create a new company
null, // profilePicture — optional avatar URL
null, // paymentPlanType — optional billing plan
'Acme Photography',
null, // credits — optional
{
inviteToken: 'YOUR_COMPANY_CREATION_TOKEN'
}
);
const { id, company_id } = await mediaviz.users.createUserAndCompany(
'Jane Smith',
'jane@example.com',
's3cur3P@ss',
null, // companyId — omit to create a new company
null, // profilePicture — optional avatar URL
null, // paymentPlanType — optional billing plan
'Acme Photography',
null, // credits — optional
{
inviteToken: 'YOUR_COMPANY_CREATION_TOKEN'
}
);
const { client } = useMediaViz();
const { id, company_id } = await client.users.createUserAndCompany(
'Jane Smith',
'jane@example.com',
's3cur3P@ss',
null, // companyId — omit to create a new company
null, // profilePicture — optional avatar URL
null, // paymentPlanType — optional billing plan
'Acme Photography',
null, // credits — optional
{
inviteToken: 'YOUR_COMPANY_CREATION_TOKEN'
}
);
result = mediaviz.users.create_user_and_company(
'Jane Smith',
'jane@example.com',
's3cur3P@ss',
None, # companyId — omit to create a new company
None, # profilePicture — optional avatar URL
None, # paymentPlanType — optional billing plan
'Acme Photography',
None, # credits — optional
invite_token='YOUR_COMPANY_CREATION_TOKEN'
)
# result['id'], result['company_id']
$result = $mediaviz->users->createUserAndCompany(
'Jane Smith',
'jane@example.com',
's3cur3P@ss',
null, // companyId — omit to create a new company
null, // profilePicture — optional avatar URL
null, // paymentPlanType — optional billing plan
'Acme Photography',
null, // credits — optional
[
'inviteToken' => 'YOUR_COMPANY_CREATION_TOKEN'
]
);
// $result['id'], $result['company_id']
curl -X POST 'https://api.mediaviz.ai/api/v1/users/new_company/?invite_token=YOUR_COMPANY_CREATION_TOKEN' \
-H 'Content-Type: application/json' \
--data @- <<'JSON'
{
"name": "Jane Smith",
"email": "jane@example.com",
"company_id": null,
"profile_picture": null,
"payment_plan_type": null,
"password": "s3cur3P@ss",
"company_name": "Acme Photography",
"credits": null
}
JSON
Response Sample
{
"id": 8507,
"company_id": 1024,
"name": "Jane Smith",
"email": "jane@example.com",
"account_type": 3
}
To add additional users to an existing company, call POST /api/v1/users/ with a name, email, account_type (1 = standard user, 2 = company admin), and the existing company_id.
Authentication & Access Tokens
The MediaViz API authenticates with OAuth2. After you initialize the SDK (see Initialize SDK Client above), get your first access token: first-party clients call authenticate(), and third-party clients call getAuthorizationUrl() and then handleCallback() after the user signs in. From then on, the SDK attaches the token to every request and refreshes it automatically, so you don't need to manage tokens yourself.
If you are integrating without the SDK, you can drive the OAuth2 Authorization Code flow (with PKCE) yourself against POST /oauth/token, then send the returned access_token in the Authorization header as a Bearer token on every request. Expand the walkthrough below for the full sequence.
Direct HTTP authentication (OAuth2 + PKCE)
These steps use curl only — replace the placeholder values (in CAPS) with your own. The SDK performs all of this for you; follow it manually only if you are integrating without the SDK.
1. Generate a PKCE verifier and challenge
Create a random code_verifier, then derive its code_challenge as the base64url-encoded SHA-256 hash of the verifier. Keep the code_verifier — you submit it at step 3. Generate a random state value too, to guard against CSRF.
2. Request an authorization code
Send the user to GET /oauth/authorize in a browser. After they sign in and approve consent, MediaViz redirects to your registered redirect_uri with a code and the state you supplied.
Authorization request
curl -G 'https://api.mediaviz.ai/oauth/authorize' \
--data-urlencode 'response_type=code' \
--data-urlencode 'client_id=YOUR_CLIENT_ID' \
--data-urlencode 'redirect_uri=https://yourapp.example.com/callback' \
--data-urlencode 'state=RANDOM_STATE' \
--data-urlencode 'code_challenge=YOUR_CODE_CHALLENGE' \
--data-urlencode 'code_challenge_method=S256'
3. Exchange the code for tokens
Exchange the authorization code at POST /oauth/token, including the code_verifier from step 1 so the server can verify the PKCE challenge. Include client_secret only if MediaViz issued you one (first-party clients). Third-party clients must leave it out, or the request fails with "Public clients must not send a client_secret." The same applies when refreshing in step 5.
Token exchange
curl -X POST 'https://api.mediaviz.ai/oauth/token' \
-H 'Content-Type: application/x-www-form-urlencoded' \
--data-urlencode 'grant_type=authorization_code' \
--data-urlencode 'code=AUTHORIZATION_CODE' \
--data-urlencode 'redirect_uri=https://yourapp.example.com/callback' \
--data-urlencode 'client_id=YOUR_CLIENT_ID' \
--data-urlencode 'client_secret=YOUR_CLIENT_SECRET' \
--data-urlencode 'code_verifier=YOUR_CODE_VERIFIER'
Response Sample
{
"access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
"refresh_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
"token_type": "bearer",
"expires_in": 86400
}
4. Call the API with the access token
Send the access_token as a Bearer token on every authenticated request.
Authenticated request
curl 'https://api.mediaviz.ai/api/v1/photos/z-photos-1024-a1b2c3d4/' \
-H 'Authorization: Bearer ACCESS_TOKEN'
5. Refresh the access token
The access_token is valid for 24 hours (expires_in is in seconds). The refresh_token lasts 30 days — when the access_token expires, exchange the refresh_token for a new one at the same endpoint, with no user interaction required.
Refresh the token
curl -X POST 'https://api.mediaviz.ai/oauth/token' \
-H 'Content-Type: application/x-www-form-urlencoded' \
--data-urlencode 'grant_type=refresh_token' \
--data-urlencode 'refresh_token=YOUR_REFRESH_TOKEN' \
--data-urlencode 'client_id=YOUR_CLIENT_ID' \
--data-urlencode 'client_secret=YOUR_CLIENT_SECRET'
Create Collection
A collection is a container for a set of photos that will be processed together by MediaViz AI models. Each collection is identified by a unique project_table_name in the format z-photos-{company_id}-{uuid}.
Collections are scoped to an outcome — the type of analysis you want performed. To create a collection, call POST /api/v1/project_outcome/ with query parameters specifying the outcomes (e.g., curation) and the models to run. The response returns the project_table_name you will use for all subsequent operations on this collection.
Request Sample
const { project_table_name } = await mediaviz.projects.createProjectAndRun(
'My Wedding Collection',
1, // type — 1 = standard collection only
null, // private — optional; defaults to false
'Wedding photos 2026',
null, // directory — optional folder path
1, // photoUploadVector — should match type
null, // thumbnail — optional cover image URL
null, // runName — optional label for this run
{
outcomes: 'curation'
}
);
import type { ProjectRunDisplay } from '@mediaviz/sdk';
const { project_table_name }: ProjectRunDisplay = await mediaviz.projects.createProjectAndRun(
'My Wedding Collection',
1, // type — 1 = standard collection only
null, // private — optional; defaults to false
'Wedding photos 2026',
null, // directory — optional folder path
1, // photoUploadVector — should match type
null, // thumbnail — optional cover image URL
null, // runName — optional label for this run
{
outcomes: 'curation'
}
);
const { project_table_name } = await mediaviz.projects.createProjectAndRun(
'My Wedding Collection',
1, // type — 1 = standard collection only
null, // private — optional; defaults to false
'Wedding photos 2026',
null, // directory — optional folder path
1, // photoUploadVector — should match type
null, // thumbnail — optional cover image URL
null, // runName — optional label for this run
{
outcomes: 'curation'
}
);
const { client } = useMediaViz();
const { project_table_name } = await client.projects.createProjectAndRun(
'My Wedding Collection',
1, // type — 1 = standard collection only
null, // private — optional; defaults to false
'Wedding photos 2026',
null, // directory — optional folder path
1, // photoUploadVector — should match type
null, // thumbnail — optional cover image URL
null, // runName — optional label for this run
{
outcomes: 'curation'
}
);
result = mediaviz.projects.create_project_and_run(
'My Wedding Collection',
1, # type — 1 = standard collection only
None, # private — optional; defaults to false
'Wedding photos 2026',
None, # directory — optional folder path
1, # photoUploadVector — should match type
None, # thumbnail — optional cover image URL
None, # runName — optional label for this run
outcomes='curation'
)
# result['project_table_name']
$result = $mediaviz->projects->createProjectAndRun(
'My Wedding Collection',
1, // type — 1 = standard collection only
null, // private — optional; defaults to false
'Wedding photos 2026',
null, // directory — optional folder path
1, // photoUploadVector — should match type
null, // thumbnail — optional cover image URL
null, // runName — optional label for this run
'curation'
);
// $result['project_table_name']
curl -X POST 'https://api.mediaviz.ai/api/v1/project_outcome/?outcomes=curation' \
-H 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyX2lkIjoidXNyXzAxYWJjMTIzIiwiZXhwIjoxNzQzNjMwMzI5fQ.RwJF-KLLr8K5JgEtIeFiKWELcv-FDLNzMPHJ2St_wAo' \
-H 'Content-Type: application/json' \
--data @- <<'JSON'
{
"name": "My Wedding Collection",
"private": null,
"type": 1,
"description": "Wedding photos 2026",
"directory": null,
"photo_upload_vector": 1,
"thumbnail": null,
"run_name": null
}
JSON
Response Sample
{
"id": "a1b2c3d4-0000-0000-0000-000000000000",
"name": "My Wedding Collection",
"project_table_name": "z-photos-1024-a1b2c3d4-0000-0000-0000-000000000000",
"run_name": "My Wedding Collection_run_1",
"similarity_level": "medium",
"blur_model": true,
"colors_model": true,
"face_recognition_model": true,
"image_classification_model": true,
"image_comparison_model": true,
"image_describe_model": false,
"ocr_model": false,
"similarity_model": true,
"evidence_model": true,
"personhood_model": false,
...
}
This sample creates a standard collection (type 1). For continuous upload collections (type 2) and the models they support, see Collection Creation & Model Selection.
Uploading Photos
Upload each photo to the collection with uploadPhoto. You must resize each photo and Base64-encode it before calling the SDK — the SDK does not resize for you. We recommend resizing photos to fit within 512×512 px. Before uploading:
- Preserve the aspect ratio. Don't crop or stretch photos to make them square. The Resize and Encode a Photo sample below handles this for you.
- Send only the Base64 data. Don't include a
data:image/jpeg;base64,prefix infileContent.
uploadPhoto fetches the collection's upload template automatically, caches it, and sets each photo's model flags to match the collection.
Resize and Encode a Photo Sample
// Browser: resize a File or Blob with a canvas
const bitmap = await createImageBitmap(file, { imageOrientation: 'from-image' });
const scale = Math.min(1, 512 / Math.max(bitmap.width, bitmap.height));
const canvas = document.createElement('canvas');
canvas.width = Math.round(bitmap.width * scale);
canvas.height = Math.round(bitmap.height * scale);
canvas.getContext('2d').drawImage(bitmap, 0, 0, canvas.width, canvas.height);
const fileContent = canvas.toDataURL('image/jpeg', 0.85).split(',')[1];
// Browser: resize a File or Blob with a canvas
const bitmap = await createImageBitmap(file, { imageOrientation: 'from-image' });
const scale = Math.min(1, 512 / Math.max(bitmap.width, bitmap.height));
const canvas = document.createElement('canvas');
canvas.width = Math.round(bitmap.width * scale);
canvas.height = Math.round(bitmap.height * scale);
canvas.getContext('2d').drawImage(bitmap, 0, 0, canvas.width, canvas.height);
const fileContent = canvas.toDataURL('image/jpeg', 0.85).split(',')[1];
// Node.js: resize with sharp
const sharp = require('sharp');
const buffer = await sharp(inputPath)
.rotate()
.resize({ width: 512, height: 512, fit: 'inside', withoutEnlargement: true })
.jpeg({ quality: 85 })
.toBuffer();
const fileContent = buffer.toString('base64');
// React Native (Expo): resize with expo-image-manipulator
import * as ImageManipulator from 'expo-image-manipulator';
// uri, width, height come from the expo-image-picker asset
const resize = width >= height
? { width: Math.min(width, 512) }
: { height: Math.min(height, 512) };
const result = await ImageManipulator.manipulateAsync(uri, [{ resize }], {
compress: 0.85,
format: ImageManipulator.SaveFormat.JPEG,
base64: true,
});
const fileContent = result.base64;
# Python: resize with Pillow
from PIL import Image, ImageOps
import base64, io
img = ImageOps.exif_transpose(Image.open(input_path)).convert("RGB")
img.thumbnail((512, 512)) # preserves aspect ratio; never upscales
buf = io.BytesIO()
img.save(buf, format="JPEG", quality=85)
file_content = base64.b64encode(buf.getvalue()).decode()
// PHP: resize with GD
$img = imagecreatefromstring(file_get_contents($inputPath));
$exif = @exif_read_data($inputPath);
switch ($exif['Orientation'] ?? 1) {
case 3: $img = imagerotate($img, 180, 0); break;
case 6: $img = imagerotate($img, -90, 0); break;
case 8: $img = imagerotate($img, 90, 0); break;
}
$w = imagesx($img);
$h = imagesy($img);
$scale = min(1, 512 / max($w, $h));
$resized = imagescale($img, (int) round($w * $scale), (int) round($h * $scale));
ob_start();
imagejpeg($resized, null, 85);
$fileContent = base64_encode(ob_get_clean());
# Shell: resize with ImageMagick, then Base64-encode for the request body
magick input.jpg -auto-orient -resize '512x512>' -quality 85 resized.jpg
base64 -i resized.jpg > resized.b64
Request Sample
const result = await mediaviz.photoUpload.uploadPhoto(
1024,
8507,
'z-photos-1024-a1b2c3d4',
{
fileContent: '/9j/4AAQSkZJRgABAQAAAQABAAD...',
mimetype: 'image/jpeg',
filePath: '/photos/IMG_0001.jpg'
}
);
const result = await mediaviz.photoUpload.uploadPhoto(
1024,
8507,
'z-photos-1024-a1b2c3d4',
{
fileContent: '/9j/4AAQSkZJRgABAQAAAQABAAD...',
mimetype: 'image/jpeg',
filePath: '/photos/IMG_0001.jpg'
}
);
const result = await mediaviz.photoUpload.uploadPhoto(
1024,
8507,
'z-photos-1024-a1b2c3d4',
{
fileContent: '/9j/4AAQSkZJRgABAQAAAQABAAD...',
mimetype: 'image/jpeg',
filePath: '/photos/IMG_0001.jpg'
}
);
const { client } = useMediaViz();
const result = await client.photoUpload.uploadPhoto(
1024,
8507,
'z-photos-1024-a1b2c3d4',
{
fileContent: '/9j/4AAQSkZJRgABAQAAAQABAAD...',
mimetype: 'image/jpeg',
filePath: '/photos/IMG_0001.jpg'
}
);
result = mediaviz.photo_upload.upload_photo(
1024,
8507,
'z-photos-1024-a1b2c3d4',
file_content='/9j/4AAQSkZJRgABAQAAAQABAAD...',
mimetype='image/jpeg',
file_path='/photos/IMG_0001.jpg'
)
$result = $mediaviz->photoUpload->uploadPhoto(
'1024',
'8507',
'z-photos-1024-a1b2c3d4',
[
'file_content' => '/9j/4AAQSkZJRgABAQAAAQABAAD...',
'mimetype' => 'image/jpeg',
'file_path' => '/photos/IMG_0001.jpg'
]
);
# Step 1: get_project_prelim_model_request_template
curl -X GET 'https://api.mediaviz.ai/api/v1/project_outcome/z-photos-1024-a1b2c3d4' \
-H 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyX2lkIjoidXNyXzAxYWJjMTIzIiwiZXhwIjoxNzQzNjMwMzI5fQ.RwJF-KLLr8K5JgEtIeFiKWELcv-FDLNzMPHJ2St_wAo'
# Step 2: Upload a preprocessed photo with metadata in the JSON body
curl -X POST 'https://upload.mediaviz.ai/photo_upload' \
-H 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyX2lkIjoidXNyXzAxYWJjMTIzIiwiZXhwIjoxNzQzNjMwMzI5fQ.RwJF-KLLr8K5JgEtIeFiKWELcv-FDLNzMPHJ2St_wAo' \
-H 'Content-Type: application/json' \
--data @- <<'JSON'
{
"file_content": "/9j/4AAQSkZJRgABAQAAAQABAAD...",
"file_path": "/photos/IMG_0001.jpg",
"mimetype": "image/jpeg",
"company_id": 1024,
"user_id": 8507,
"project_table_name": "z-photos-1024-a1b2c3d4",
"blur": "<BLUR>",
"colors": "<COLORS>",
"face_recognition": "<FACE_RECOGNITION>",
"image_classification": "<IMAGE_CLASSIFICATION>",
"image_comparison": "<IMAGE_COMPARISON>",
"image_describe": "<IMAGE_DESCRIBE>",
"ocr": "<OCR>"
}
JSON
Mark Photo Upload Complete
Once all photos have been uploaded, signal to MediaViz that the upload phase is finished. This triggers the AI processing pipeline to begin. Call:
POST /api/v1/project/{project_table_name}/upload_complete/
After this call, the collection transitions to a processing state and the models begin running.
Request Sample
const result = await mediaviz.projects.markProjectUploadComplete('z-photos-1024-a1b2c3d4');
const result = await mediaviz.projects.markProjectUploadComplete('z-photos-1024-a1b2c3d4');
const result = await mediaviz.projects.markProjectUploadComplete('z-photos-1024-a1b2c3d4');
const { client } = useMediaViz();
const result = await client.projects.markProjectUploadComplete('z-photos-1024-a1b2c3d4');
result = mediaviz.projects.mark_project_upload_complete('z-photos-1024-a1b2c3d4')
$result = $mediaviz->projects->markProjectUploadComplete('z-photos-1024-a1b2c3d4');
curl -X POST 'https://api.mediaviz.ai/api/v1/project/z-photos-1024-a1b2c3d4/upload_complete/' \
-H 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyX2lkIjoidXNyXzAxYWJjMTIzIiwiZXhwIjoxNzQzNjMwMzI5fQ.RwJF-KLLr8K5JgEtIeFiKWELcv-FDLNzMPHJ2St_wAo'
Check Run Status
Poll the status endpoint to track processing progress. The response includes an overall progress_percentage as well as an individual completion flag for each model that was configured for the collection.
GET /api/v1/project/status/{project_table_name}
Request Sample
const result = await mediaviz.projects.checkProjectStatus('z-photos-1024-a1b2c3d4');
const result = await mediaviz.projects.checkProjectStatus('z-photos-1024-a1b2c3d4');
const result = await mediaviz.projects.checkProjectStatus('z-photos-1024-a1b2c3d4');
const { client } = useMediaViz();
const result = await client.projects.checkProjectStatus('z-photos-1024-a1b2c3d4');
result = mediaviz.projects.check_project_status('z-photos-1024-a1b2c3d4')
$result = $mediaviz->projects->checkProjectStatus('z-photos-1024-a1b2c3d4');
curl -X GET 'https://api.mediaviz.ai/api/v1/project/status/z-photos-1024-a1b2c3d4' \
-H 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyX2lkIjoidXNyXzAxYWJjMTIzIiwiZXhwIjoxNzQzNjMwMzI5fQ.RwJF-KLLr8K5JgEtIeFiKWELcv-FDLNzMPHJ2St_wAo'
progress_percentage is a fraction between 0 and 1 — not a 0-100 percent, so a value of 0.72 means the run is 72% done. Each configured model also has its own <model>_model_complete boolean.
Response Sample
{
"progress_percentage": 0.72,
"blur_model_complete": true,
"colors_model_complete": true,
"face_recognition_model_complete": false,
"image_classification_model_complete": false,
"similarity_model_complete": false,
"evidence_model_complete": false
}
Continue polling until progress_percentage reaches 1 and every model flag is true before fetching results.
View Analysis Results
Once processing is complete, retrieve results at the collection level or for individual photos.
Get all photo IDs — use GET /api/v1/photos/{table_name}/ to retrieve the photo IDs in the collection, sorted by date taken. asc_or_desc sets the direction (asc, the default, returns the oldest first). Photos without a date taken are ordered by photo ID. To be sorted by date, a photo's date taken must be sent as dateTaken when it's uploaded.
- Similar photos are grouped by default. Only the best photo from each group of similar photos is returned, plus any photo with no similar matches. Pass
include_all=trueto return every photo. - Results are paginated. Use
limitfor the page size, and pass the last photo ID you received aslast_idto get the next page. - Filter by date taken. Use
start_dateandend_dateas ISO 8601 date-times (for examplestart_date=2024-01-01T00:00:00andend_date=2024-01-31T23:59:59) to limit results to a date range, orno_date_taken=trueto get only photos without a date taken.
Request Sample — All Photo IDs
const result = await mediaviz.photos.getProjectPhotoIdsByTableName(
'z-photos-1024-a1b2c3d4',
{
ascOrDesc: 'asc',
limit: 100
}
);
const result = await mediaviz.photos.getProjectPhotoIdsByTableName(
'z-photos-1024-a1b2c3d4',
{
ascOrDesc: 'asc',
limit: 100
}
);
const result = await mediaviz.photos.getProjectPhotoIdsByTableName(
'z-photos-1024-a1b2c3d4',
{
ascOrDesc: 'asc',
limit: 100
}
);
const { client } = useMediaViz();
const result = await client.photos.getProjectPhotoIdsByTableName(
'z-photos-1024-a1b2c3d4',
{
ascOrDesc: 'asc',
limit: 100
}
);
result = mediaviz.photos.get_project_photo_ids_by_table_name('z-photos-1024-a1b2c3d4', asc_or_desc='asc', limit=100)
$result = $mediaviz->photos->getProjectPhotoIdsByTableName(
'z-photos-1024-a1b2c3d4',
[
'ascOrDesc' => 'asc',
'limit' => 100
]
);
curl -X GET 'https://api.mediaviz.ai/api/v1/photos/z-photos-1024-a1b2c3d4/?asc_or_desc=asc&limit=100' \
-H 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyX2lkIjoidXNyXzAxYWJjMTIzIiwiZXhwIjoxNzQzNjMwMzI5fQ.RwJF-KLLr8K5JgEtIeFiKWELcv-FDLNzMPHJ2St_wAo'
Response Sample
[
[1035601],
[1035602],
[1035603]
]
Get photo IDs by tier — use GET /api/v1/photos/ranked/{table_name}/ to retrieve photo IDs grouped into three tiers by evidence_score: top_tier (top 15%), tier_2 (middle 70%), and tier_3 (bottom 15%). Only the best photo from each group of similar photos is included. Within each tier, photos are ordered by date taken.
The tier endpoint accepts the same asc_or_desc, limit, last_id, and date filters (include_all doesn't apply). When paging, pass the last photo ID from the last non-empty tier as last_id.
Request Sample — Photo IDs by Tier
const { top_tier, tier_2, tier_3 } = await mediaviz.photos.getRankedProjectPhotoIdsByTableName('z-photos-1024-a1b2c3d4');
const { top_tier, tier_2, tier_3 } = await mediaviz.photos.getRankedProjectPhotoIdsByTableName('z-photos-1024-a1b2c3d4');
const { top_tier, tier_2, tier_3 } = await mediaviz.photos.getRankedProjectPhotoIdsByTableName('z-photos-1024-a1b2c3d4');
const { client } = useMediaViz();
const { top_tier, tier_2, tier_3 } = await client.photos.getRankedProjectPhotoIdsByTableName('z-photos-1024-a1b2c3d4');
result = mediaviz.photos.get_ranked_project_photo_ids_by_table_name('z-photos-1024-a1b2c3d4')
# result['top_tier'], result['tier_2'], result['tier_3']
$result = $mediaviz->photos->getRankedProjectPhotoIdsByTableName('z-photos-1024-a1b2c3d4');
// $result['top_tier'], $result['tier_2'], $result['tier_3']
curl -X GET 'https://api.mediaviz.ai/api/v1/photos/ranked/z-photos-1024-a1b2c3d4/' \
-H 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyX2lkIjoidXNyXzAxYWJjMTIzIiwiZXhwIjoxNzQzNjMwMzI5fQ.RwJF-KLLr8K5JgEtIeFiKWELcv-FDLNzMPHJ2St_wAo'
Response Sample
{
"top_tier": [1035601, 1035602],
"tier_2": [1035603, 1035604, 1035605, 1035606, 1035607],
"tier_3": [1035608, 1035609]
}
Individual Photo Results
Retrieve detailed analysis for a single photo using GET /api/v1/photos/{table_name}/{photo_id}. The response includes the full set of model outputs for that photo, including:
- blur_value — sharpness score
- main_color_palette — dominant colors extracted from the image
- labels_from_classifications_model — image classification tags, always an object keyed by label with a
0-1confidence value ({ "Sky": 0.99, "Beach": 0.95 }), never a plain array; read the keys for a bare tag list.nulluntil image classification has run. - bounding_boxes_from_faces_model — detected face regions
- similar_photo_ids_high / _medium / _low — similar photos grouped by similarity strength
- evidence_score — overall curation score
- face_score — quality score for face content
- content_score — subject matter quality score
- aesthetic_score — compositional and aesthetic quality score
- similarity_set_ranking — rank within its similarity cluster
Request Sample
const result = await mediaviz.photos.getPhotoFromProject('z-photos-1024-a1b2c3d4', 1035601);
import type { PhotoDisplay } from '@mediaviz/sdk';
const result: PhotoDisplay = await mediaviz.photos.getPhotoFromProject('z-photos-1024-a1b2c3d4', 1035601);
const result = await mediaviz.photos.getPhotoFromProject('z-photos-1024-a1b2c3d4', 1035601);
const { client } = useMediaViz();
const result = await client.photos.getPhotoFromProject('z-photos-1024-a1b2c3d4', 1035601);
result = mediaviz.photos.get_photo_from_project('z-photos-1024-a1b2c3d4', 1035601)
$result = $mediaviz->photos->getPhotoFromProject('z-photos-1024-a1b2c3d4', 1035601);
curl -X GET 'https://api.mediaviz.ai/api/v1/photos/z-photos-1024-a1b2c3d4/1035601' \
-H 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyX2lkIjoidXNyXzAxYWJjMTIzIiwiZXhwIjoxNzQzNjMwMzI5fQ.RwJF-KLLr8K5JgEtIeFiKWELcv-FDLNzMPHJ2St_wAo'
Response Sample
{
"id": 1035601,
"blur_value": 0.95,
"main_color_palette": ["#3a5a8c", "#f0e6d2", "#2c2c2c"],
"labels_from_classifications_model": {
"outdoor": 0.98,
"portrait": 0.95,
"natural light": 0.89
},
"bounding_boxes_from_faces_model": [
{ "x": 120, "y": 45, "width": 80, "height": 90 }
],
"similar_photo_ids_high": [1035607],
"similar_photo_ids_medium": [1035612, 1035619],
"similar_photo_ids_low": [1035631],
"evidence_score": 0.91,
"face_score": 0.88,
"content_score": 0.84,
"aesthetic_score": 0.79,
"similarity_set_ranking": 1
}