Skip to content

GogoScreen API

Make product videos from a URL

GogoScreen works through a real website and turns what it did into a narrated video. This API is that service, callable from your own code over REST or from an agent over MCP. Everything it makes is billed in seconds of finished video out of one wallet. Nothing here is a mock: an operation either records the real site or it reads what an earlier recording produced.

What you need to know in 60 seconds

Base URLhttps://api.gogoscreen.com/api/v1
MCPhttps://api.gogoscreen.com/mcp
OpenAPIhttps://api.gogoscreen.com/api/v1/openapi.json
AuthAuthorization: Bearer gsk_live_…
ErrorsAlways { error: { code, message, details?, requestId } }. Branch on code, never on message, and keep a default branch.
Long workAnswers 202 with a Location. Poll it, wait pollAfterMs, stop on succeeded, failed or cancelled.
WritesEvery one takes an Idempotency-Key header, and it is required rather than optional.
ListsCursor paged. limit 1 to 100, defaulting to 20 on most lists and to 100 on a few; follow nextCursor until it is null.
Rate limitsRateLimit-Limit, RateLimit-Remaining and RateLimit-Reset on every answer, including refusals. 600 reads a minute per credential, fewer for the rest.
MoneySeconds of finished video, reserved when work is queued and charged at the measured length. A failed or cancelled job is charged nothing.

The two things it makes

These names are used consistently in every page, every field and every error code, so it is worth ten seconds to learn them.

A walkthroughA marketing video
What it isA recording of a real product in use, not a mockup, narrated.A designed promotional film, built from a scanned website or from a walkthrough you already made.
The chainstoryboard then rendersource then generation then project with a revision then export
Start it withPOST /storyboards to plan, then POST /storyboards/{id}/renders. Or POST /renders to do both in one call.POST /marketing/sources, then POST /marketing/generations, then POST /marketing/projects/{id}/exports.
What spends secondsThe render. Planning is free of video time.The export. Scanning, generating and editing are all free of video time.
The scope that spendsrenders:writemarketing:export
Editable afterwardsNo. A render is a file. Re-plan and record again.Yes. Every revision is kept, so every change is undoable.

Around both sit the things that support them: the catalogue of voices, designs and render options; the wallet and its ledger; saved render templates; usage; API keys; and webhooks. Every one is in the reference, generated from the server's own declarations.

Quickstart

From nothing to a downloaded MP4 in five calls. The slow part is the render itself, and polling tells you when it is done.

Each step continues the one above it. The five blocks of a language are one script: the imports, BASE and the auth header are set up in step 1, the render id comes out of step 3, and steps 4 and 5 use both. Run a block on its own and it will not find them.

Get a key

Sign in and open the developer console. Name the key after the thing that will use it, tick the scopes that thing needs, and copy the value: it is shown once and cannot be recovered later. For this quickstart you need account:read, renders:read and renders:write.

renders:write is one of the 2 scopes that can spend the account's video time. The picker marks both.

API and MCP access comes with any paid plan or a top up. Creating a key needs one, and every call a key makes checks it again: an account with no live plan and no top up time left is answered 403 paid_account_required until it buys time, and then the same key works again. The free seconds given at signup do not count; they are for the web app.

The account's email address has to be verified as well. Starting a render and fetching a finished file both declare it, and a key inherits the fact from the account it belongs to rather than from the day it was issued, so an account that loses verification meets 403 email_unverified on a key that worked yesterday.

1. Check the key and the balance

export GOGOSCREEN_API_KEY=gsk_live_…

curl https://api.gogoscreen.com/api/v1/account -H "Authorization: Bearer $GOGOSCREEN_API_KEY"
json
{
  "account": {
    "id": "3f2b0c1a-9d4e-4a61-b8c2-000000000001",
    "email": "docs-funded-d81d104d@docs.local",
    "name": "Docs examples",
    "emailVerified": true,
    "role": "user",
    "createdAt": "2026-09-21T11:00:00.000Z",
    "deletionStatus": "active"
  },
  "wallet": {
    "planSeconds": 0,
    "planId": null,
    "planExpiresAt": null,
    "planActive": false,
    "paygSeconds": 542,
    "freeSeconds": 0,
    "reservedSeconds": 0,
    "availableSeconds": 542
  },
  "credential": {
    "kind": "api_key",
    "apiKeyId": "3f2b0c1a-9d4e-4a61-b8c2-000000000002",
    "scopes": [
      "account:read",
      "catalog:read",
      "marketing:export",
      "marketing:read",
      "marketing:write",
      "renders:read",
      "renders:write",
      "storyboards:read",
      "storyboards:write",
      "usage:read",
      "wallet:read",
      "webhooks:manage"
    ]
  }
}
200. Executed. Ids and timestamps are normalised; the shape is the real one.

credential.scopes is what this key can actually do, and wallet.availableSeconds is the number every reservation is judged against.

2. Ask what it will cost

renders.quote prices the options and creates nothing. It is the cheapest way to find out whether the account can afford a video and whether the file will carry the free tier mark.

curl -X POST https://api.gogoscreen.com/api/v1/renders/quote \
  -H "Authorization: Bearer $GOGOSCREEN_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"targetSeconds": 60}'
json
{
  "secondsToReserve": 60,
  "watermark": false,
  "capped": false,
  "targetSeconds": 60,
  "maxSeconds": null,
  "sufficient": true,
  "needed": null,
  "availableSeconds": 542
}
200. Executed.
NameTypeDescription
secondsToReserveintegerWhat will be held when the render is queued.
watermarkbooleanWhether the file will carry the free tier mark, which happens only when the seconds come out of the free bucket.
cappedbooleanTrue when an Auto ceiling was lowered to what the balance can afford. targetSeconds and maxSeconds then say what it was lowered to.
sufficientbooleanWhether the render would be accepted right now.
neededinteger | nullHow many seconds it would take, when sufficient is false. Null otherwise.
availableSecondsintegerThe wallet's spendable balance.

3. Start the render

POST /renders plans and records in one call: it visits the real site, works out how to show what hint asks for, narrates it and delivers an MP4. Two things are required, the url and the hint, and one header is required, the idempotency key.

# Keep the response headers: Location is the only thing you need from this call.
curl -sS -D headers.txt -o created.json -X POST https://api.gogoscreen.com/api/v1/renders \
  -H "Authorization: Bearer $GOGOSCREEN_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: $(uuidgen)" \
  -d '{
    "url": "https://www.iana.org",
    "hint": "Show the protocol registries page.",
    "targetSeconds": 30
  }'

# 202 Accepted, Location: /api/v1/renders/<id>
LOCATION=$(tr -d '\r' < headers.txt | awk 'tolower($1) == "location:" { print $2 }')
RENDER=$(basename "$LOCATION")
echo "$RENDER"
http
HTTP/1.1 202 Accepted
Location: /api/v1/renders/3f2b0c1a-9d4e-4a61-b8c2-000000000025
RateLimit-Limit: 20
RateLimit-Remaining: 19
RateLimit-Reset: 60
Cache-Control: private, no-store

{
  "resource": {
    "id": "3f2b0c1a-9d4e-4a61-b8c2-000000000025",
    "kind": "walkthrough.render",
    "state": "queued",
    "internalStatus": "queued",
    "stage": null,
    "progress": null,
    "error": null,
    "result": null,
    "cost": {
      "secondsReserved": 30,
      "secondsCharged": null,
      "watermark": false
    },
    "artifactsExpired": false,
    "cancellable": true,
    "idempotencyKey": "render-1790012026461",
    "requestId": null,
    "createdAt": "2026-09-21T11:21:00.000Z",
    "startedAt": null,
    "finishedAt": null,
    "expiresAt": null,
    "pollAfterMs": 5000
  },
  "created": true
}
202. Executed once against a live server and cancelled immediately, so the seconds went back whole.

Location is where to poll. cost.secondsReserved is what has been held. The work is queued, not done: state is queued and result is null.

4. Poll until it settles

Wait pollAfterMs, ask again, stop when state is terminal. While it runs, progress is the service's own reading, so a waiting screen can show a real number rather than a spinner.

# $RENDER is the id step 3 read out of the Location header.
while : ; do
  body=$(curl -sS "https://api.gogoscreen.com/api/v1/renders/$RENDER" -H "Authorization: Bearer $GOGOSCREEN_API_KEY")
  state=$(echo "$body" | jq -r '.render.state')
  echo "$state $(echo "$body" | jq -r '.render.progress.percent // "-"')%"
  case "$state" in succeeded|failed|cancelled) break ;; esac
  sleep "$(echo "$(echo "$body" | jq -r '.render.pollAfterMs') / 1000" | bc -l)"
done
http
HTTP/1.1 200 OK
RateLimit-Limit: 600
RateLimit-Remaining: 596
RateLimit-Reset: 60
Cache-Control: private, no-store

{
  "render": {
    "id": "3f2b0c1a-9d4e-4a61-b8c2-000000000025",
    "kind": "walkthrough.render",
    "state": "running",
    "internalStatus": "running",
    "stage": null,
    "progress": {
      "stage": "starting",
      "percent": 3,
      "at": "2026-09-21T11:24:00.000Z"
    },
    "error": null,
    "result": null,
    "cost": {
      "secondsReserved": 30,
      "secondsCharged": null,
      "watermark": false
    },
    "artifactsExpired": false,
    "cancellable": true,
    "idempotencyKey": "render-1790012026461",
    "requestId": null,
    "createdAt": "2026-09-21T11:21:00.000Z",
    "startedAt": "2026-09-21T11:21:00.000Z",
    "finishedAt": null,
    "expiresAt": null,
    "pollAfterMs": 2500
  }
}
200, a moment after the create. Executed.
http
HTTP/1.1 200 OK
RateLimit-Limit: 600
RateLimit-Remaining: 579
RateLimit-Reset: 13
Cache-Control: private, no-store

{
  "render": {
    "id": "3f2b0c1a-9d4e-4a61-b8c2-000000000026",
    "kind": "walkthrough.render",
    "state": "succeeded",
    "internalStatus": "succeeded",
    "stage": null,
    "progress": null,
    "error": null,
    "result": {
      "artifacts": [],
      "durationSeconds": 29
    },
    "cost": {
      "secondsReserved": 30,
      "secondsCharged": 30,
      "watermark": false
    },
    "artifactsExpired": false,
    "cancellable": false,
    "idempotencyKey": "quickstart-1790012026518",
    "requestId": null,
    "createdAt": "2026-09-21T11:21:00.000Z",
    "startedAt": "2026-09-21T11:25:00.000Z",
    "finishedAt": "2026-09-21T11:26:00.000Z",
    "expiresAt": null,
    "pollAfterMs": null
  }
}
The same job at the end of the walk. Executed, after fifty polls that each waited the resource's own pollAfterMs. pollAfterMs is now null, cancellable is false, and cost.secondsCharged is the finished film's measured length against a hold of thirty.

5. Download the file

GET /renders/{id}/download answers a short lived signed URL and when it stops working. The bytes are never proxied through this API: fetch that URL directly.

link=$(curl -sS "https://api.gogoscreen.com/api/v1/renders/$RENDER/download" \
  -H "Authorization: Bearer $GOGOSCREEN_API_KEY" | jq -r '.url')

# The bytes are never proxied through this API. Fetch the signed URL directly.
curl -sS -o walkthrough.mp4 "$link"
NameTypeDescription
urlstringA signed URL. Short lived, single purpose, and not shareable.
expiresAtstringWhen the URL stops working, ISO 8601.
http
HTTP/1.1 200 OK
RateLimit-Limit: 600
RateLimit-Remaining: 578
RateLimit-Reset: 13
Cache-Control: private, no-store

{
  "url": "https://files.gogoscreen.com/renders/3f2b0c1a-9d4e-4a61-b8c2-000000000026.mp4?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Content-Sha256=UNSIGNED-PAYLOAD&X-Amz-Credential=REDACTED&X-Amz-Date=20260921T173634Z&X-Amz-Expires=300&X-Amz-Signature=REDACTED&X-Amz-SignedHeaders=host&x-id=GetObject",
  "expiresAt": "2026-09-21T11:27:00.000Z"
}
200, executed against the render that finished. The URL is signed, so its credential and signature are redacted here; every other parameter is the real one.
http
HTTP/1.1 409 Conflict

{
  "error": {
    "code": "not_ready",
    "message": "That video is not finished yet.",
    "details": { "status": "cancelled" },
    "requestId": "a2745367-0afb-44ee-851e-84c0bf75721f"
  }
}
409 not_ready, executed against a render that had been cancelled. details.status is the render's own status word.

Stopping one

A render that is queued is cancelled before it starts; one that is running stops at its next checkpoint. Either way the reservation is released whole and nothing is charged.

http
HTTP/1.1 200 OK
RateLimit-Limit: 120
RateLimit-Remaining: 115
RateLimit-Reset: 60
Cache-Control: private, no-store

{
  "resource": {
    "id": "3f2b0c1a-9d4e-4a61-b8c2-000000000025",
    "kind": "walkthrough.render",
    "state": "cancelled",
    "internalStatus": "cancelled",
    "stage": null,
    "progress": null,
    "error": null,
    "result": null,
    "cost": {
      "secondsReserved": 30,
      "secondsCharged": 0,
      "watermark": false
    },
    "artifactsExpired": false,
    "cancellable": false,
    "idempotencyKey": "render-1790012026461",
    "requestId": null,
    "createdAt": "2026-09-21T11:21:00.000Z",
    "startedAt": "2026-09-21T11:21:00.000Z",
    "finishedAt": "2026-09-21T11:21:00.000Z",
    "expiresAt": null,
    "pollAfterMs": null
  },
  "released": true,
  "removedFromQueue": false
}
200. Executed. secondsCharged is 0 and released says the hold went back.

What it costs

The unit is one second of finished video. The account has a wallet of them, and everything on this surface is free except two operations: recording a walkthrough, and exporting a marketing video.

ReservesCharges
Planning a walkthrough, scanning a website, generating a marketing project, editing a revisionNothingNothing.
A walkthrough renderIts length when it is queued. An Auto video holds its ceiling, capped to the balance.The finished file's measured length. The difference goes back.
A marketing exportThe delivered length when it is queued.The finished file's measured length.
A job that failed or was cancelledReleased wholeNothing.

Free seconds carry a watermark. The wallet spends plan seconds first, then pay as you go, then free, because that order loses the customer the least. The consequence is that free seconds are spent last, so a video that paid time covers whole is never watermarked, and a file any of whose seconds came out of the free bucket carries the mark. renders.quote tells you before you start, and cost.watermark on the job says what actually happened.

GET /wallet is the balance by bucket, and GET /wallet/ledger is every movement with the balance as it stood after each one.

Calling it from an agent

Every operation an API key may call is also an MCP tool, generated from the same declaration, so the tool list and the key operations in this documentation are the same list. Managing keys and the support assistant are the exceptions: they belong to a signed in dashboard session and are neither key operations nor tools. Point a host at https://api.gogoscreen.com/mcp with your key in an Authorization header, or run the stdio bridge for a host that cannot send one.

About the examples in these pages

Every response printed in this documentation was taken from a real exchange with a running server, unless a note beside it says otherwise. Every operation reference page under the reference is generated from the server's own declarations rather than written.

What is replaced in a captured response: UUIDs, which keep a stable stand in per distinct value, ISO timestamps, API keys and webhook secrets, the credential and signature parameters of signed file URLs, the server origin, the webhook receiver, and the capture's own idempotency keys. What is never touched: every field name, every field order, every value type and every HTTP status. Email addresses are not normalised, and the one you will see belongs to a throwaway test account.

Two honest limits. Where an operation cannot be run without spending money it is shown in its refusal path and says so in a sentence. And many response fields in the generated reference carry a name, a type and no description, because a description has not been written for every field yet; a blank meaning there is a gap in the API, not a field that means nothing.