Submitter API is an API, where the Submitters can upload / manage their users videos in a easy and seamless maner.
The Submitter API uploads videos and model releases to your Glymt account and follows them through review to publication and sales. Files go straight to Glymt's storage through presigned URLs — no storage account of your own is needed, and nothing you upload is ever made public. API version 1.0; https only — http is refused.
API clients are issued by Glymt on request. Glymt reviews the request and issues a client ID and secret.
Every request of the Submitter API goes to this base URL. Only https is accepted: a request over http is refused.
Every answer is JSON with its own HTTP status. An unknown path under the base answers 404 with
{"error": "not_found", "documentation": …}.
https://api.glymt.com/upload/v1The Submitter API uses OAuth 2.0 client credentials (RFC 6749 §4.4). Glymt issues you a client ID and a client secret; the secret is shown once, when it is issued — keep it on your server. If it is lost or exposed, ask us for a new one: the old one stops working at once and every token issued with it is revoked.
Request a token from POST /oauth/token with grant_type=client_credentials, the client by HTTP Basic
(client_id:client_secret) or by client_id and client_secret in the form body, never both.
A user's username or password is never accepted.
Send the token on every other call: Authorization: Bearer {access_token}. A token lasts 7200 seconds
(expires_in); then request a new one. A Submitter API token is refused by the Reseller API and the reverse
(401 invalid_token).
Errors: 400 invalid_request / unsupported_grant_type, 401 invalid_client (with WWW-Authenticate) on the token
request; 401 invalid_token on every other call.
curl -s -X POST https://api.glymt.com/upload/v1/oauth/token \
-u '{client_id}:{client_secret}' \
-d grant_type=client_credentials
{
"access_token": "{access_token}",
"token_type": "Bearer",
"expires_in": 7200,
"scope": "upload"
}
curl -s https://api.glymt.com/upload/v1/account/summary \
-H 'Authorization: Bearer {access_token}'
Every endpoint has its own path. Base URL https://api.glymt.com/upload/v1.
| Method | Path | Endpoint |
|---|---|---|
| GET | / |
welcome (no token) |
| POST | /oauth/token |
token |
| POST | /batches/open |
open an upload batch |
| POST | /batches/upload_url/:batch_id |
presigned upload URL for one file (video, release, csv) |
| POST | /batches/submit/:batch_id |
submit the batch's metadata |
| GET | /videos/list |
the submitter's videos and their state |
| GET | /videos/status/:video_id |
one video's state (video id or submitter_video_id) |
| DELETE | /videos/remove/:video_id |
remove a video |
| POST | /releases/register |
register an uploaded model release |
| GET | /releases/list |
the submitter's releases |
| GET | /releases/details/:release_id |
one release with a 5-minute signed link |
| DELETE | /releases/remove/:release_id |
remove a release |
| GET | /account/summary |
statistics, read only |
| GET | /account/sales |
sales with dates, paged |
POST /batches/open answers a batch_id. Your upload quota is checked
first (403 / 429 with reset_at).POST /batches/upload_url/:batch_id with the file's
kind (video, release or csv), filename, size in bytes and content_type.url you received, with the Content-Type header it names, within
an hour. One PUT always suffices (a video is at most 2 GB).POST /releases/register — or name them in the rows
of the next step.POST /batches/submit/:batch_id, JSON rows (or a CSV file of the batch). Each
row answers accepted or refused with its reason.GET /videos/status/:video_id by your submitter_video_id, or
GET /videos/list.Filenames: letters, digits, -, _ and . only — no slash, no ... A video is a file
the intake accepts, from 1 MB to 2 GB; a release is a PDF, JPEG or PNG of at most 20 MB; a CSV file at most 5 MB.
curl -s -X POST https://api.glymt.com/upload/v1/batches/open \
-H 'Authorization: Bearer {access_token}'
curl -s -X POST https://api.glymt.com/upload/v1/batches/upload_url/{batch_id} \
-H 'Authorization: Bearer {access_token}' \
-H 'Content-Type: application/json' \
-d '{ "kind": "video", "filename": "beach_sunset_01.mp4", "size": 734003200, "content_type": "video/mp4" }'
curl -s -X PUT '{presigned_upload_url}' \
-H 'Content-Type: video/mp4' \
--upload-file beach_sunset_01.mp4
curl -s -X POST https://api.glymt.com/upload/v1/batches/upload_url/{batch_id} \
-H 'Authorization: Bearer {access_token}' \
-H 'Content-Type: application/json' \
-d '{ "kind": "release", "filename": "release_ana.pdf", "size": 182044, "content_type": "application/pdf" }'
curl -s -X PUT '{presigned_upload_url}' -H 'Content-Type: application/pdf' --upload-file release_ana.pdf
curl -s -X POST https://api.glymt.com/upload/v1/batches/submit/{batch_id} \
-H 'Authorization: Bearer {access_token}' \
-H 'Content-Type: application/json' \
-d '{
"videos": [
{
"file": "beach_sunset_01.mp4",
"title": "Sunset over the beach",
"description": "Golden hour on a quiet beach",
"keywords": ["beach", "sunset", "sea", "waves"],
"categories": ["Nature", "Travel"],
"is_ai": false,
"submitter_video_id": "SV-0001",
"releases": ["release_ana.pdf"],
"publish": true
}
]
}'
Instead of JSON rows, upload a CSV file to the batch (kind csv) and submit {"csv": "<its filename>"}.
The first row is the header; the columns, in this order:
| # | Column | Content |
|---|---|---|
| 1 | file | the video's filename in the batch |
| 2 | title | at most 200 characters |
| 3 | keywords | separated by ; |
| 4 | releases | release filenames in the batch, or ids of releases already registered, separated by ; |
| 5 | submitter_video_id | your own id for the video (letters, digits, - _ . ; at most 64) |
| 6 | is_ai | true | false |
| 7 | description | at most 5,000 characters |
| 8 | categories | Glymt category names, separated by ; |
| 9 | publish | true | false (empty: true) — published by itself once checked and approved, or left unpublished |
Download the template: submitter_api_template.csv
(file,title,keywords,releases,submitter_video_id,is_ai,description,categories,publish).
curl -s -X POST https://api.glymt.com/upload/v1/batches/submit/{batch_id} \
-H 'Authorization: Bearer {access_token}' \
-H 'Content-Type: application/json' \
-d '{ "csv": "batch_metadata.csv" }'
| Field | Type | Meaning |
|---|---|---|
submitter_video_id | string or null | your id for it |
video_id, token, title | integer, string, string — or null | the video, once the intake created it |
status | string | queued, processing, awaiting_approval, published, rejected, ignored, error |
reason | string or null | why it was rejected, ignored or refused |
lifecycle_state | string or null | the video's state on Glymt |
submitted_at, published_at | ISO 8601 UTC | when you submitted it and when it was published |
assets | object or null | once processed: the cover (small, medium, large) and the unbranded previews, 570×320 (preview_sd) and 1920×1080 (preview_hd) — absolute https URLs; null before |
sales_count | integer | its licences |
{
"submitter_video_id": "SV-0001",
"video_id": {video_id},
"token": "{video_token}",
"title": "Sunset over the beach",
"status": "published",
"reason": null,
"lifecycle_state": "{lifecycle_state}",
"submitted_at": "2026-10-30T12:00:00Z",
"published_at": "2026-10-31T09:00:00Z",
"assets": {
"cover": {
"small": "https://glymt.com/{cover_small_path}",
"medium": "https://glymt.com/{cover_medium_path}",
"large": "https://glymt.com/{cover_large_path}"
},
"preview_sd": "https://glymt.com/uploads/video/{video_id}/unbranded_sd.mp4",
"preview_hd": "https://glymt.com/uploads/video/{video_id}/unbranded_hd.mp4"
},
"sales_count": 0
}
| status | Meaning |
|---|---|
| queued | submitted; the intake has not created the video yet |
| processing | the video exists and is being processed |
| awaiting_approval | processed; in Glymt's review |
| published | live on Glymt |
| rejected | refused by the review, or removed (with the reason) |
| ignored, error | the intake refused the file (with the reason) |
{id, name, legal_age (adult | minor), videos_count, created_at}; its details add a signed url that lasts
300 seconds. Model releases are identity documents: never public.
The API's name, version and documentation. No token. No parameters.
curl -s https://api.glymt.com/upload/v1
{
"name": "Glymt Submitter API",
"version": "1.0",
"documentation": "https://glymt.com/submitter_api_documentation",
"description": "{summary}"
}
A Bearer token for 7200 seconds — see Authentication.
Errors: 400 invalid_request, 400 unsupported_grant_type, 401 invalid_client, 429.
curl -s -X POST https://api.glymt.com/upload/v1/oauth/token \
-u '{client_id}:{client_secret}' \
-d grant_type=client_credentials
{
"access_token": "{access_token}",
"token_type": "Bearer",
"expires_in": 7200,
"scope": "upload"
}
Opens a batch: 201 {batch_id, upload_url, submit}. No parameters. Your quota is checked first.
Errors: 400 invalid_request, 401 invalid_token, 403 (the quota refuses uploads now, with its reason), 429 monthly_ceiling (with
reset_at).
curl -s -X POST https://api.glymt.com/upload/v1/batches/open \
-H 'Authorization: Bearer {access_token}'
HTTP/1.1 201 Created
{
"batch_id": "{batch_id}",
"upload_url": "/upload/v1/batches/upload_url/{batch_id}",
"submit": "/upload/v1/batches/submit/{batch_id}"
}
HTTP/1.1 429 Too Many Requests
{ "error": "monthly_ceiling", "reset_at": "2026-11-01T00:00:00Z" }
A presigned PUT URL (one hour) for one file of the batch. Body: kind (video | release | csv), filename,
size (bytes), content_type (a video: video/*; a release: application/pdf,
image/jpeg or image/png; a CSV: text/csv). Send the PUT with exactly the
Content-Type the answer names.
Errors: 400 invalid_request (a kind, filename, size or type out of its limits), 401 invalid_token, 404 not_found (a batch that was not opened), 422 (a video the intake would refuse: its size or format).
curl -s -X POST https://api.glymt.com/upload/v1/batches/upload_url/{batch_id} \
-H 'Authorization: Bearer {access_token}' \
-H 'Content-Type: application/json' \
-d '{ "kind": "video", "filename": "beach_sunset_01.mp4", "size": 734003200, "content_type": "video/mp4" }'
{
"batch_id": "{batch_id}",
"kind": "video",
"file": "beach_sunset_01.mp4",
"method": "PUT",
"url": "{presigned_upload_url}",
"headers": { "Content-Type": "video/mp4" },
"expires_in": 3600
}
curl -s -X PUT '{presigned_upload_url}' \
-H 'Content-Type: video/mp4' \
--upload-file beach_sunset_01.mp4
The batch's metadata: JSON {"videos": [row, …]} (1 – 500 rows) or {"csv": "<filename>"}. A row:
file (required, uploaded to the batch), title, description, keywords (list),
categories (Glymt category names), is_ai, submitter_video_id (unique per account),
releases (release filenames of the batch or ids of registered releases), publish (default true).
publish true (or omitted): the video is published by itself once it passes our checks (and, for a new
account, our approval) and goes straight to our automated review and curation. false: it waits unpublished in your
account; open it on its edit page on Glymt to finish it and publish it.
publish true (or omitted): the video is published by itself once it passes our checks (and, for a new
account, our approval) and goes straight to our automated review and curation. false: it waits unpublished in your
account; open it on its edit page on Glymt to finish it and publish it.
publish true (or omitted): the video is published by itself once it passes our checks (and, for a new
account, our approval) and goes straight to our automated review and curation. false: it waits unpublished in your
account; open it on its edit page on Glymt to finish it and publish it.
Every row is checked against the batch and admitted by your quota, then handed to Glymt's intake. The answer lists every row
(accepted or refused with its reason). When the quota refuses a row, the batch stops there: that row and the
ones after it are refused, the rows before stay accepted, and the answer is 403 or 429 with reset_at.
Errors: 400 invalid_request (malformed body), 401 invalid_token, 403 / 429 (the quota), 404 not_found (the batch).
curl -s -X POST https://api.glymt.com/upload/v1/batches/submit/{batch_id} \
-H 'Authorization: Bearer {access_token}' \
-H 'Content-Type: application/json' \
-d '{
"videos": [
{
"file": "beach_sunset_01.mp4",
"title": "Sunset over the beach",
"description": "Golden hour on a quiet beach",
"keywords": ["beach", "sunset", "sea", "waves"],
"categories": ["Nature", "Travel"],
"is_ai": false,
"submitter_video_id": "SV-0001",
"releases": ["release_ana.pdf"],
"publish": true
}
]
}'
{
"batch_id": "{batch_id}",
"accepted": 1,
"refused": 1,
"rows": [
{ "submitter_video_id": "SV-0001", "file": "beach_sunset_01.mp4", "result": "accepted", "reason": null, "submitted_video_id": {submission_id} },
{ "submitter_video_id": "SV-0002", "file": "city_02.mp4", "result": "refused", "reason": "file city_02.mp4 is not in the batch (upload it to its upload_url first)", "submitted_video_id": null }
]
}
Your submitted videos, newest first: page (from 1), per_page (1 – 100, default 50).
Errors: 400 invalid_request, 401 invalid_token.
curl -s 'https://api.glymt.com/upload/v1/videos/list?page=1&per_page=50' \
-H 'Authorization: Bearer {access_token}'
{
"count": {submissions},
"page": 1,
"per_page": 50,
"videos": [ {Upload}, … ]
}
One video by your submitter_video_id or its Glymt video id: {video: Upload}.
Errors: 400 invalid_request, 401 invalid_token, 404 not_found.
curl -s https://api.glymt.com/upload/v1/videos/status/SV-0001 \
-H 'Authorization: Bearer {access_token}'
{ "video": {Upload} }
Removes one of your videos (by submitter_video_id or video id): 202.
Errors: 400 invalid_request, 401 invalid_token, 404 not_found, 409 not_a_video_yet (still queued), 423 locked (a lock-in holds the video).
curl -s -X DELETE https://api.glymt.com/upload/v1/videos/remove/{video_id} \
-H 'Authorization: Bearer {access_token}'
HTTP/1.1 202 Accepted
{ "video_id": {video_id}, "state": "removing" }
Registers a model release uploaded to a batch (kind release). Body: batch_id, file,
name (1 – 120 characters), legal_age (adult | minor). 202 while it registers; the same file
again answers 200 with the existing release.
Errors: 400 invalid_request, 401 invalid_token, 404 not_found (the file is not in the batch).
curl -s -X POST https://api.glymt.com/upload/v1/releases/register \
-H 'Authorization: Bearer {access_token}' \
-H 'Content-Type: application/json' \
-d '{ "batch_id": "{batch_id}", "file": "release_ana.pdf", "name": "Ana", "legal_age": "adult" }'
HTTP/1.1 202 Accepted
{ "name": "Ana", "file": "release_ana.pdf", "state": "registering", "list": "/upload/v1/releases/list" }
Your releases (metadata only): page, per_page (1 – 100, default 50).
Errors: 400 invalid_request, 401 invalid_token.
curl -s 'https://api.glymt.com/upload/v1/releases/list?page=1&per_page=50' \
-H 'Authorization: Bearer {access_token}'
{
"count": 1,
"page": 1,
"per_page": 50,
"releases": [
{ "id": {release_id}, "name": "Ana", "legal_age": "adult", "videos_count": 1, "created_at": "2026-10-30T12:00:00Z" }
]
}
One release and a signed link to its file that lasts 300 seconds.
Errors: 400 invalid_request, 401 invalid_token, 404 not_found.
curl -s https://api.glymt.com/upload/v1/releases/details/{release_id} \
-H 'Authorization: Bearer {access_token}'
{
"release": {
"id": {release_id}, "name": "Ana", "legal_age": "adult", "videos_count": 1, "created_at": "2026-10-30T12:00:00Z",
"url": "{signed_release_url}", "expires_in": 300
}
}
Removes one of your releases: 202.
Errors: 400 invalid_request, 401 invalid_token, 404 not_found.
curl -s -X DELETE https://api.glymt.com/upload/v1/releases/remove/{release_id} \
-H 'Authorization: Bearer {access_token}'
HTTP/1.1 202 Accepted
{ "release_id": {release_id}, "state": "removing" }
Read only: your client, your videos by status, published and sales counts, your earnings and unpaid earnings, and your upload quota (tier, this month admitted and its ceiling, the reset, the lifetime allowance). No parameters.
Errors: 400 invalid_request, 401 invalid_token.
curl -s https://api.glymt.com/upload/v1/account/summary \
-H 'Authorization: Bearer {access_token}'
{
"client": { "name": "{client_name}", "client_id": "{client_id}" },
"videos": { "submitted": 12, "processing": 1, "awaiting_approval": 2, "published": 8, "rejected": 1, "removed": 0 },
"published_count": 8,
"sales_count": 3,
"earnings": 21.5,
"unpaid_earnings": 7.25,
"quota": {
"tier": "{tier}",
"month_admitted": 12,
"monthly_ceiling": {monthly_ceiling},
"resets_at": "2026-11-01T00:00:00Z",
"lifetime_admitted": 12,
"lifetime_allowance": {lifetime_allowance}
}
}
Your sales, newest first: page, per_page (1 – 100, default 50), and optionally start_date with
end_date (YYYY-MM-DD, together). Each sale: video_id, sold_at, channel — which videos sold
and when; your totals are in /account/summary.
Errors: 400 invalid_request, 401 invalid_token.
curl -s 'https://api.glymt.com/upload/v1/account/sales?page=1&start_date=2026-10-01&end_date=2026-10-31' \
-H 'Authorization: Bearer {access_token}'
{
"count": 1,
"page": 1,
"per_page": 50,
"sales": [
{ "video_id": {video_id}, "sold_at": "2026-10-20T15:00:00Z", "channel": "Glymt" }
]
}
Every error is JSON with its HTTP status — {"error": code, "message": text}, or the OAuth form
{"error": code, "error_description": text} for the token and the Bearer checks. Nothing answers 200 with an error.
{ "error": "invalid_request", "message": "unknown parameter(s): bucket" }
| Status | error | When |
|---|---|---|
| 400 | invalid_request, unsupported_grant_type | a missing, unknown or malformed parameter; a malformed body |
| 401 | invalid_client | the token request: unknown client, wrong secret, inactive client or owner |
| 401 | invalid_token | no token, or one unknown, expired, revoked or issued for the Reseller API |
| 402 | — | not answered by the Submitter API |
| 403 | the quota's reason | the upload quota refuses uploads now |
| 404 | not_found | an unknown path, batch, video or release |
| 409 | not_a_video_yet | removing a video the intake has not created yet |
| 413 | — | a request body above the size limit, refused before it reaches the API (files go by their presigned URL) |
| 415 | — | not answered by v1: a body that is neither JSON nor form-encoded is not read, and the request answers 400 |
| 422 | the intake's reason | an upload URL for a video the intake would refuse (size or format) |
| 423 | locked | removing a video a lock-in holds |
| 429 | monthly_ceiling, too_many_requests | the monthly upload ceiling (with reset_at), or a rate limit (with Retry-After) |
| 500 | server_error | an error on Glymt's side; retry later |
{ "error": "not_found", "documentation": "https://glymt.com/submitter_api_documentation" }
429 with Retry-After (seconds)./account/summary); a batch holds up to 500 rows; an upload URL lasts one
hour.API version 1.0.
/videos, /releases and /users endpoints are removed.