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.
Keep these credentials out of your source code. Store them in an environment file (.env) at the root of your project. The client secret is optional — include it only if MediaViz issued you one.
Create an Environment File
# .env
MEDIAVIZ_CLIENT_ID=YOUR_CLIENT_ID
# Optional — include only if MediaViz issued you a client secret
MEDIAVIZ_CLIENT_SECRET=YOUR_CLIENT_SECRET
# .env
MEDIAVIZ_CLIENT_ID=YOUR_CLIENT_ID
# Optional — include only if MediaViz issued you a client secret
MEDIAVIZ_CLIENT_SECRET=YOUR_CLIENT_SECRET
# .env
MEDIAVIZ_CLIENT_ID=YOUR_CLIENT_ID
# Optional — include only if MediaViz issued you a client secret
MEDIAVIZ_CLIENT_SECRET=YOUR_CLIENT_SECRET
# .env
MEDIAVIZ_CLIENT_ID=YOUR_CLIENT_ID
# Optional — include only if MediaViz issued you a client secret
MEDIAVIZ_CLIENT_SECRET=YOUR_CLIENT_SECRET
# .env
MEDIAVIZ_CLIENT_ID=YOUR_CLIENT_ID
# Optional — include only if MediaViz issued you a client secret
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',
});
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
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'
);
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'
);
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'
);
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'
)
# 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'
);
// $result['id'], $result['company_id']
curl -X POST 'https://api.mediaviz.ai/api/v1/users/new_company/' \
-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"
}
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. Once you initialize the SDK with your client ID and secret (see Initialize SDK Client above), the SDK acquires an access token, attaches it to every request, and refreshes it automatically — 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.
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',
null, // private — optional; defaults to false
null, // type — optional project type id
'Wedding photos 2026',
null, // directory — optional folder path
2,
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',
null, // private — optional; defaults to false
null, // type — optional project type id
'Wedding photos 2026',
null, // directory — optional folder path
2,
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',
null, // private — optional; defaults to false
null, // type — optional project type id
'Wedding photos 2026',
null, // directory — optional folder path
2,
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',
None, # private — optional; defaults to false
None, # type — optional project type id
'Wedding photos 2026',
None, # directory — optional folder path
2,
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',
null, // private — optional; defaults to false
null, // type — optional project type id
'Wedding photos 2026',
null, // directory — optional folder path
2,
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": null,
"description": "Wedding photos 2026",
"directory": null,
"photo_upload_vector": 2,
"thumbnail": null,
"run_name": null
}
JSON
Response Sample
{
"project_table_name": "z-photos-1024-a1b2c3d4",
"outcomes": ["curation"],
"models": ["blur", "colors", "face_recognition", "image_classification", "similarity", "evidence"],
"status": "created"
}
Uploading Photos
Upload each photo in the collection with uploadPhoto. The SDK fetches the collection's upload template on the first call, caches it, and posts the photo to the dedicated upload service — all in a single call.
You must resize each photo to exactly 256×256 px and Base64-encode it before calling the SDK — the SDK does not resize for you. Call uploadPhoto once per photo, passing the collection's project_table_name, the company ID, user ID, a photo index, and the photo object (including its title and Base64 body).
Request Sample
const result = await mediaviz.photoUpload.uploadPhoto(
'z-photos-1024-a1b2c3d4',
1024,
8507,
0,
{
title: 'beach-sunset.jpg',
fileContent: '<base64-encoded photo>',
mimetype: 'image/jpeg',
filePath: 'beach-sunset.jpg'
}
);
const result = await mediaviz.photoUpload.uploadPhoto(
'z-photos-1024-a1b2c3d4',
1024,
8507,
0,
{
title: 'beach-sunset.jpg',
fileContent: '<base64-encoded photo>',
mimetype: 'image/jpeg',
filePath: 'beach-sunset.jpg'
}
);
const result = await mediaviz.photoUpload.uploadPhoto(
'z-photos-1024-a1b2c3d4',
1024,
8507,
0,
{
title: 'beach-sunset.jpg',
fileContent: '<base64-encoded photo>',
mimetype: 'image/jpeg',
filePath: 'beach-sunset.jpg'
}
);
result = mediaviz.photo_upload.upload_photo(
'z-photos-1024-a1b2c3d4',
1024,
8507,
0,
title='beach-sunset.jpg',
file_content='<base64-encoded photo>',
mimetype='image/jpeg',
file_path='beach-sunset.jpg'
)
$result = $mediaviz->photoUpload->uploadPhoto(
'z-photos-1024-a1b2c3d4',
1024,
8507,
0,
[
'title' => 'beach-sunset.jpg',
'file_content' => '<base64-encoded photo>',
'mimetype' => 'image/jpeg',
'file_path' => 'beach-sunset.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 via headers
curl -X POST 'https://upload.mediaviz.ai/photo_upload' \
-H 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyX2lkIjoidXNyXzAxYWJjMTIzIiwiZXhwIjoxNzQzNjMwMzI5fQ.RwJF-KLLr8K5JgEtIeFiKWELcv-FDLNzMPHJ2St_wAo' \
-H 'x-company-id: 1024' \
-H 'x-user-id: 8507' \
-H 'x-project-table-name: z-photos-1024-a1b2c3d4' \
-H 'x-title: beach-sunset.jpg' \
-H 'Content-Type: application/json' \
--data @- <<'JSON'
{
"file_content": "<base64-encoded photo>",
"mimetype": "image/jpeg",
"file_path": "beach-sunset.jpg"
}
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/
Include your refresh_token in the Authorization header. 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');
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}
progress_percentage is a fraction between 0 and 1. Each configured model also has its own boolean. Continue polling until progress_percentage reaches 1 and every model flag is true before fetching results.
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
}
progress_percentage is a decimal between 0 and 1 — a value of 1 means project processing is complete and results are ready to fetch.
View Analysis Results
Once processing is complete, retrieve results at the collection level or for individual photos.
Get all photos sorted by score — use GET /api/v1/photos/{table_name}/ to retrieve every photo in the collection, passing the asc_or_desc query parameter (asc or desc) to control sort order.
Request Sample — All Photos Sorted
const result = await mediaviz.photos.getProjectPhotoIdsByTableName(
'z-photos-1024-a1b2c3d4',
{
ascOrDesc: 'asc'
}
);
const result = await mediaviz.photos.getProjectPhotoIdsByTableName(
'z-photos-1024-a1b2c3d4',
{
ascOrDesc: 'asc'
}
);
const result = await mediaviz.photos.getProjectPhotoIdsByTableName(
'z-photos-1024-a1b2c3d4',
{
ascOrDesc: 'asc'
}
);
result = mediaviz.photos.get_project_photo_ids_by_table_name('z-photos-1024-a1b2c3d4', asc_or_desc='asc')
$result = $mediaviz->photos->getProjectPhotoIdsByTableName(
'z-photos-1024-a1b2c3d4',
[
'ascOrDesc' => 'asc'
]
);
curl -X GET 'https://api.mediaviz.ai/api/v1/photos/z-photos-1024-a1b2c3d4/?asc_or_desc=asc' \
-H 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyX2lkIjoidXNyXzAxYWJjMTIzIiwiZXhwIjoxNzQzNjMwMzI5fQ.RwJF-KLLr8K5JgEtIeFiKWELcv-FDLNzMPHJ2St_wAo'
Response Sample
[
{ "id": 1035601, "evidence_score": 0.91 },
{ "id": 1035602, "evidence_score": 0.87 },
{ "id": 1035603, "evidence_score": 0.54 }
]
Get top photos by tier — use GET /api/v1/photos/ranked/{table_name}/ to retrieve photos grouped into three tiers based on their evidence_score: best (top 15%), middle (middle 70%), and worst (bottom 15%).
Request Sample — Photos by Tier
const { best, middle, worst } = await mediaviz.photos.getRankedProjectPhotoIdsByTableName('z-photos-1024-a1b2c3d4');
const { best, middle, worst } = await mediaviz.photos.getRankedProjectPhotoIdsByTableName('z-photos-1024-a1b2c3d4');
const { best, middle, worst } = await mediaviz.photos.getRankedProjectPhotoIdsByTableName('z-photos-1024-a1b2c3d4');
result = mediaviz.photos.get_ranked_project_photo_ids_by_table_name('z-photos-1024-a1b2c3d4')
# result['best'], result['middle'], result['worst']
$result = $mediaviz->photos->getRankedProjectPhotoIdsByTableName('z-photos-1024-a1b2c3d4');
// $result['best'], $result['middle'], $result['worst']
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
{
"best": [1035601, 1035602],
"middle": [1035603, 1035604, 1035605, 1035606, 1035607],
"worst": [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 as a
{ label: confidence }map - 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);
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
}