API and MCP server
Read a product, write its scripts, price an AI UGC video and make it from your own tools or an AI assistant, with a key from your team's Settings, then change it, make it longer, or have it in another language or another size. Each call is a press you can make in the app, at the same price.
Get a key
The API comes with a plan: Lite, Starter, Growth, Scale. It is not in the free trial.
- Sign in as the owner of your team and open Settings, then API.
- Type a name for the key, such as the tool that will use it, and press Create key.
- Copy the key. It is shown once, when it is made.
A key acts for your whole team and makes videos with your team's credits. Settings shows each key's name, its last 4 characters and when it was last used. Press Revoke there to stop a key at once. A team holds up to 10 keys.
Send the key
Every call is under https://app.sayitagain.ai/api/v1. Send the key in the Authorization header of every request, after Bearer, and send a body as JSON, or an ad's video as a form (POST /ads).
curl https://app.sayitagain.ai/api/v1/balance \
-H "Authorization: Bearer $SIA_KEY"
Every answer to a request with a good key carries your team's balance in the X-Credits-Balance header. Messages are in the language your team reads the app in, and codes are always the same.
The calls
GET /balance
Your team's credits, its plan and how many free scripts presses are left.
Free.
Answer
{
"credits": 4000,
"on_hold": 0,
"plan": {"id": "growth", "name": "Growth"},
"scripts_left": 41
}
POST /products
Read a product from its link, as the app does when you choose one. The answer's id names the product in the other calls.
Free, and counted against your team's product reads: 30 an hour and 150 a day. A product read in the last 10 minutes is not read again and counts nothing.
curl https://app.sayitagain.ai/api/v1/products \
-H "Authorization: Bearer $SIA_KEY" \
-H "Content-Type: application/json" \
-d '{"url": "https://yourstore.com/products/cast-iron-skillet"}'
Answer
{
"product": {
"id": "7b3bbd9038bb1be9",
"url": "https://yourstore.com/products/cast-iron-skillet",
"name": "Cast Iron Skillet",
"price": "$49",
"currency": "USD",
"category": "cookware",
"description": "A cast iron skillet that holds its heat.",
"benefits": ["Holds heat for a deep sear"],
"photos": ["https://yourstore.com/cdn/skillet.jpg"],
"angles": [{"id": "auto", "name": "Auto"}, {"id": "problem_solution", "name": "Problem / solution"}, {"id": "objection", "name": "The objection"}]
}
}
GET /products/{id}
A product your team has read, by its id, with the angles a video of it can argue.
Free.
POST /scripts
Write 3 scripts for a video of a product, to read before you make it. It answers at once with an id: the scripts take about a minute.
Free, and counted against your team's free scripts presses, as Write 3 scripts is in the app. A product not read in the last 10 minutes is read again, and counts against your team's product reads.
It takes product, and any of the video's fields below but count and photos.
curl https://app.sayitagain.ai/api/v1/scripts \
-H "Authorization: Bearer $SIA_KEY" \
-H "Content-Type: application/json" \
-d '{"product": "7b3bbd9038bb1be9", "description": "Searing a steak on a weeknight."}'
Answer
{
"id": "a1b2c3d4e5f6",
"status": "queued",
"product": {"id": "7b3bbd9038bb1be9", "url": "https://yourstore.com/products/cast-iron-skillet"}
}
GET /scripts/{id}
The scripts, once written. status is queued, in_progress, ready or failed. Each script has what the presenter does, its scene, its sound, its clothes, the other people in it, and its shots, each with its camera, what happens, who speaks, how it is said and what they say.
Free.
Answer
{
"id": "a1b2c3d4e5f6",
"status": "ready",
"size": "9:16",
"scripts": [
{
"number": 1,
"title": "Your description",
"what_they_do": "Searing a steak on a weeknight.",
"scene": "a kitchen with a gas hob",
"sound": "a sizzle, no music",
"clothes": "",
"other_people": "",
"shots": [
{"camera": "close on the pan", "what_happens": "They lay the steak in.",
"speaker": "", "how_it_is_said": "calm", "what_they_say": "Listen to that sear."}
]
}
]
}
POST /quote
What a video costs before anything is spent, and whether your balance covers it. It takes the video's fields below that change the price, and script_id.
Free.
curl https://app.sayitagain.ai/api/v1/quote \
-H "Authorization: Bearer $SIA_KEY" \
-H "Content-Type: application/json" \
-d '{"quality": "fast", "count": 2}'
Answer
{
"credits": 800,
"videos": 2,
"each": 400,
"balance": 4000,
"enough": true
}
POST /videos
Make an AI UGC video: a generated presenter films themselves with your product. It answers at once with an id.
Spends credits: the price /quote gives for the same fields, taken when the video starts and refunded if it fails. When your balance does not cover it, it is refused with 402 and nothing is spent. A product not read in the last 10 minutes is read again, and counts against your team's product reads.
curl https://app.sayitagain.ai/api/v1/videos \
-H "Authorization: Bearer $SIA_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: skillet-1" \
-d '{"product": "7b3bbd9038bb1be9", "quality": "fast", "size": "9:16"}'
Answer
{
"id": "f6e5d4c3b2a1",
"status": "queued",
"credits": 400,
"videos": 1,
"product": {"id": "7b3bbd9038bb1be9", "url": "https://yourstore.com/products/cast-iron-skillet"}
}
Send an Idempotency-Key header with any text unique to the video, up to 200 characters. A retry with the same key and the same body answers as the first request did and spends nothing more, for a day. The same key with another body is refused.
| Field | What it takes |
|---|---|
product | The product's id from /products, or its link. Required. |
description | Describe the video: where it happens, what happens and what it should get across, in up to 600 characters. Words it gives someone to say, in quotes or not, are said word for word. |
lines | A list of lines to say, one a shot: 3 to 5 for a 15-second video, up to 10 for 30 seconds. They are said as you send them, with no translation and no checks. With script_id, they take the place of that script's lines, in order, and are not checked. |
script_id, script | Make one of the scripts /scripts wrote: its id, and its number, from 1. |
quality | fast, the default, or best. |
seconds | Fast is 15 seconds. Best is 15 or 30. |
size | 9:16 vertical, 3:4 portrait, 1:1 square, 16:9 landscape. 9:16 is the default. |
count | How many videos, from 1 to 4, each a different take. Not with a script, which is made once for each presenter. |
presenters | Up to 3 presenter ids from /presenters. Left out, one is chosen for the product. |
language | A code from /languages. Left out, your team's ad language. |
languages | Up to 3 codes from /languages, to make a script in each at once, as the app's scripts page does: each language is a video of its own at one video's price, and the answer has an id for each in ids, the first in id. The first takes the place of language. Only with script_id, and not with language or lines; a script whose words are said as typed is made in one language, and the Arabic dialects, voiced after filming at their own price, go together, apart from every other language. |
captions | true, the default, or false. |
video_style | casual, professional, energetic. Casual is the default. |
placement | Where the product is: auto, hand, surface, room, worn. Auto is the default. |
angle | The angle the video argues: auto, the default, or one of the angles /products answers for the product, as the app's Angle offers them. Not with lines or script_id, which argue their own. |
photos | Photo links from /products to film the product from. |
ad | An ad to build the video like, as the app's Build one like it does: its moves are made for your product, and the ad itself is never filmed. A link of an ad in the Meta Ad Library, an ad's id from /ads or the library, an ad your team uploaded, with POST /ads or in the app, as u- and its id, or one of your own videos as v- and its id, with -v2 and on for the others of its press. A library link not seen before is looked up in the library and counted against your team's 20 ad links a day. Not with lines or script_id. |
keep | What to keep of the ad: structure, the default, or pacing, its pace and its shots alone. |
credits | The price /quote gave for the same fields. Optional here: when it is not what the video costs now, the video is refused with 409 price_changed, the price now in the message and in error.credits, and nothing is spent. Required over MCP (below). |
GET /videos/{id}
How a video is going: queued, in_progress, ready or failed, with each video the press made. Once one is ready, its download_url opens its file for 60 minutes, until download_expires_at, in UTC; download_expires_in_seconds is the seconds it has left when the answer is made. Ask again for a new link. It is the file the app's Library downloads. AI UGC videos, product videos and image ads made by Say It Again carry Content Credentials. Each file says it was generated, and which tool made it. Say It Again is a conformant Generator Product on the C2PA™ Conforming Products List.
Free.
Answer
{
"id": "f6e5d4c3b2a1",
"status": "ready",
"created_at": "2026-10-18T09:30:00Z",
"product": {"id": "7b3bbd9038bb1be9", "url": "https://yourstore.com/products/cast-iron-skillet",
"name": "Cast Iron Skillet"},
"language": "en-us",
"videos": [
{"part": "v1", "status": "ready", "seconds": 15.0, "size": "9:16", "presenter": "Priya",
"credits": 400, "download_url": "https://app.sayitagain.ai/api/v1/videos/f6e5d4c3b2a1/file?pass=...",
"download_expires_at": "2026-10-18T10:36:00Z", "download_expires_in_seconds": 3600,
"after_steps": {"change_video": {"credits": 400, "free_video": false},
"other_sizes": {"credits": 0, "sizes": ["4:5", "1:1", "16:9"]}}}
]
}
A video that failed says why in error, and its credits are refunded.
A ready video lists in after_steps the steps its page in the app offers next, each with its price in credits: change_video, make_longer, another_language (with the voiced languages, the Arabic dialects, at their own price) and other_sizes, free. A step it does not offer is not listed. Each step answers an id to ask here, as a video does.
POST /videos/{id}/change
Change a finished video as you describe it, made again into a new video beside it, as the app's Change does: change, what to change, in up to 300 characters, and part, which of its videos, v1 by default. It is checked as in the app: no price, and nothing that gives the presenter a past with the product, 20 checks a day. Only for a video whose after_steps offers change_video: one made in the last 30 days whose take can still be sent back. It answers at once with the new video's id, to ask GET /videos/{id} with.
Spends credits: the price after_steps lists, refunded if it fails, and one of the key's presses of the day. Send an Idempotency-Key, and credits, the price after_steps lists, as for /videos.
curl https://app.sayitagain.ai/api/v1/videos/f6e5d4c3b2a1/change \
-H "Authorization: Bearer $SIA_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: skillet-change-1" \
-d '{"change": "Make the shirt navy blue"}'
Answer
{
"id": "1a2b3c4d5e6f",
"status": "queued",
"credits": 400,
"from": {"id": "f6e5d4c3b2a1", "part": "v1"}
}
POST /videos/{id}/longer
Make a finished video longer, as the app's Make this video 30 seconds does: its take is carried on with new lines written from your product page and checked as every script is, into a new video beside it. part is which of its videos, v1 by default. Only for a video whose after_steps offers make_longer. It answers at once with the new video's id.
Spends credits: the price after_steps lists, refunded if it fails, and one of the key's presses of the day. The product's page is read again, as for /videos. Send an Idempotency-Key, and credits, the price after_steps lists, as for /videos.
curl -X POST https://app.sayitagain.ai/api/v1/videos/f6e5d4c3b2a1/longer \
-H "Authorization: Bearer $SIA_KEY" \
-H "Idempotency-Key: skillet-longer-1"
Answer
{
"id": "2b3c4d5e6f7a",
"status": "queued",
"credits": 500,
"seconds": 30,
"from": {"id": "f6e5d4c3b2a1", "part": "v1"}
}
POST /videos/{id}/language
Make a finished video again in another language, as the app's Another language does: the same script, from the English it was written in, the same presenter, photos and scene, made again by the video model in language, a code after_steps lists for it. part is which of its videos, v1 by default. A voiced language, one of the Arabic dialects, is voiced after filming at its own price. It answers at once with the new video's id.
Spends credits: the price after_steps lists for that language, refunded if it fails, and one of the key's presses of the day. The product's page is read again, as for /videos. Send an Idempotency-Key, and credits, the price after_steps lists for that language, as for /videos.
curl https://app.sayitagain.ai/api/v1/videos/f6e5d4c3b2a1/language \
-H "Authorization: Bearer $SIA_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: skillet-es-1" \
-d '{"language": "es-mx"}'
Answer
{
"id": "3c4d5e6f7a8b",
"status": "queued",
"credits": 400,
"videos": 1,
"language": "es-mx",
"from": {"id": "f6e5d4c3b2a1", "part": "v1"}
}
POST /videos/{id}/size
Lay a finished video out in another size, the same video cut for that frame, as the app's Other sizes does: size, one after_steps lists for it, offered for a video filmed 9:16. It answers at once with the new video's id. The same size of the same video asked again answers with the one you have: 202 while it is being made, 200 and "status": "ready" once it is made.
Free, and one of the key's presses of the day when it lays a new one out.
curl https://app.sayitagain.ai/api/v1/videos/f6e5d4c3b2a1/size \
-H "Authorization: Bearer $SIA_KEY" \
-H "Content-Type: application/json" \
-d '{"size": "1:1"}'
Answer
{
"id": "4d5e6f7a8b9c",
"status": "queued",
"credits": 0,
"size": "1:1",
"from": {"id": "f6e5d4c3b2a1"}
}
GET /videos
Your team's AI UGC videos, newest first, 50 at most a page. Send limit for fewer, and after with the next of a page for the page after it.
Free.
GET /presenters
The presenters a video can be made with, by id: ours, and the ones your team described. GET /languages lists the languages a video is spoken in, by code, and your team's own. GET /sizes lists the sizes and the qualities with their lengths.
Free.
GET /ads
The ads of the Meta Ad Library your store already holds, to make a video like one with ad: your Competitors, longest run first, the Winning ads of your store's kind, and the library links your team pasted. Each has the id ad takes, its advertiser, its title, how many days it has run, and whether it is a video, which ad needs. Send source (competitors, winning or pasted) for one alone, and limit for up to 50.
Free. Nothing is looked up in the library.
curl "https://app.sayitagain.ai/api/v1/ads?source=competitors" \
-H "Authorization: Bearer $SIA_KEY"
Answer
{
"ads": [
{"id": "a-1234567890123456", "source": "competitors", "advertiser": "Rival Pans",
"title": "The pan that holds its heat", "days_running": 48, "video": true}
]
}
POST /ads
Upload an ad's video to make a video like it with ad, as the app's Upload an ad does. Send it as multipart/form-data: the video in file, an MP4 or a MOV in H.264, or in HEVC as an iPhone films it, up to 2 minutes, under 30 MB and no larger than 4K (4096 by 2304 pixels); and rights_confirmed as true, which says your team has the right to use the ad. An upload without it is refused. It is recorded with the upload, with the key that sent it and when. The answer's id is the ad that /scripts, /quote and /videos take. The MCP server's tools take no upload.
Free, and counted against your team's 20 uploads an hour, the app's and the API's together. Your team keeps its 6 newest uploads. One never used goes after 7 days, and the video of one used is deleted once it is read, while its id still works as ad.
curl https://app.sayitagain.ai/api/v1/ads \
-H "Authorization: Bearer $SIA_KEY" \
-F "file=@morning-pan.mov" \
-F "rights_confirmed=true"
Answer
{
"id": "u-0123456789abcdef",
"name": "morning-pan",
"seconds": 14.5
}
POST /presenters/describe
Describe a presenter of your team's own, as the app's Describe a presenter does: description, how they look, in up to 300 characters, and a name if you want one, up to 24. They are an adult and invented, never a real person or a character; a description that is not is refused with why. It answers at once with an id: the 3 pictures take about a minute.
Free, and counted against your team's 5 drawings a day, as in the app. A description refused, or a drawing that fails, gives its drawing back.
curl https://app.sayitagain.ai/api/v1/presenters/describe \
-H "Authorization: Bearer $SIA_KEY" \
-H "Content-Type: application/json" \
-d '{"description": "A woman in her thirties with short grey hair and a denim shirt", "name": "Noor"}'
Answer
{
"id": "0a1b2c3d4e5f",
"status": "checking"
}
GET /presenters/describe/{id}
How a drawing is going: checking, drawing, ready or refused. Once ready, each picture has its number and a url that opens it for 60 minutes, and voice is the voice the description said, woman or man, or empty when it said none. A refused drawing says why in error.
Free.
Answer
{
"id": "0a1b2c3d4e5f",
"status": "ready",
"pictures": [
{"number": 1, "url": "https://app.sayitagain.ai/api/v1/presenters/describe/0a1b2c3d4e5f/1.jpg?pass=..."}
],
"voice": "woman"
}
POST /presenters/keep
Keep one picture of a drawing as one of your team's presenters: the drawing's id, the picture's number, and the voice they speak in, woman or man, which an Arabic video needs. The answer's id names the presenter in presenters.
Free.
curl https://app.sayitagain.ai/api/v1/presenters/keep \
-H "Authorization: Bearer $SIA_KEY" \
-H "Content-Type: application/json" \
-d '{"id": "0a1b2c3d4e5f", "picture": 2, "voice": "woman"}'
Answer
{
"presenter": {"id": "d:9f8e7d6c5b4a", "name": "Noor", "voice": "woman"}
}
POST /presenters/voice
Say or change the voice one of your described presenters speaks in: presenter, their id from /presenters, and voice, woman or man. An Arabic video with a described presenter nobody has given a voice is refused until one is said.
Free.
curl https://app.sayitagain.ai/api/v1/presenters/voice \
-H "Authorization: Bearer $SIA_KEY" \
-H "Content-Type: application/json" \
-d '{"presenter": "d:9f8e7d6c5b4a", "voice": "man"}'
Waiting for a video
There are no webhooks. Ask GET /videos/{id} every 30 seconds or so until its status is ready or failed. A video takes a few minutes.
Errors
An error is a status and a body of one shape. The code is for your code to read, and the message for a person.
{
"error": {
"code": "insufficient_credits",
"message": "This costs 400 credits and you have 120."
}
}
| Status | Codes |
|---|---|
| 400 | invalid_json: the body is not a JSON object. invalid_upload: an upload's body is not a form with its video. |
| 401 | missing_key, invalid_key, revoked_key: no key, a key that is not right, or one that was revoked. |
| 402 | insufficient_credits: your balance does not cover the video. |
| 403 | plan_required: your team has no plan. allowance_used: the free scripts presses are used. product_not_allowed: in ChatGPT, a kind of product it makes no ads for (below). |
| 404 | not_found: your team has nothing with that id. |
| 409 | scripts_not_ready: the scripts are still being written. price_changed: the credits you sent are not what it costs now, which the message says and error.credits holds; nothing is spent. |
| 413 | too_large: the body is over 64 KB, or an ad's video over 30 MB. |
| 422 | invalid_request, invalid_product, product_unreadable, refused, idempotency_conflict: a field to change, said in the message. ad_refused: an ad that cannot be used, or an uploaded video that cannot be read. rights_required: an upload sent without rights_confirmed as true. |
| 429 | rate_limited, daily_limit, product_reads, busy, ad_uploads: a limit, with Retry-After in seconds. |
| 500 | server_error: something went wrong at our end. Try again in a minute. |
| 503 | product_not_checked: in ChatGPT, the product could not be checked. Try again in a minute. ad_busy: other ads are being read; try again after Retry-After. |
Limits
- 60 requests a minute for each key, and 240 requests a minute for the files opened by the links it signs: videos' files and the pictures our display shows.
- 20 presses of
POST /videosa day for each key, counted in UTC. - Your team's own limits in the app: product reads, free scripts presses, videos in progress at once, and 20 uploads of ads an hour.
A request past a limit is answered 429, with Retry-After saying how many seconds to wait.
MCP server
An AI assistant can make your videos through our MCP server, at https://app.sayitagain.ai/mcp, over Streamable HTTP. It takes the same key, in the same Authorization header, or signs you in to Say It Again instead (below), and each tool is one of the calls above.
Each tool that spends, make_video, change_video, make_longer and another_language, takes credits, the price quote_video or after_steps gave, and an idempotency_key, every time: the price is in what your assistant's app shows you before the call, and a retry with the same key spends nothing more. A price that is not what it costs now is refused with price_changed, and nothing is spent.
Claude Code
claude mcp add --transport http sayitagain https://app.sayitagain.ai/mcp \
--header "Authorization: Bearer $SIA_KEY"
Or leave the header out, and sign in when Claude Code asks.
Other clients
Add a remote MCP server with the address above. Where the client's form takes an API key or a bearer token, give it your key.
Tools
| Tool | What it does |
|---|---|
get_balance | The team's credits, its plan, and how many write_scripts calls it has left (scripts_left). Free. |
read_product | Read a product's page from its link: its name, price, description, benefits, photos and the angles a video of it can argue, and an id to use in the other tools. Free, and counted against the team's product reads. |
write_scripts | Write three scripts for a UGC video of a product, to read before making one. Say It Again's own writer writes them from the product read_product read, held to its facts. Free, and counted against the calls get_balance gives as scripts_left. Answers an id at once: call get_scripts with it about once a minute until its status is ready or failed. |
get_scripts | Scripts written by write_scripts: queued, in_progress, ready with each script's shots and lines, or failed. Free. |
quote_video | What a video with these choices costs in credits, and whether the team's balance covers it. Free. |
make_video | Make a UGC video of a product. This spends the team's credits: the price quote_video gives for the same choices, refunded if the video fails. Answers an id at once, and with languages an id for each language in ids: call get_video with each about once a minute until its status is ready or failed, which takes a few minutes. |
get_video | How a video from make_video, or from a step after one, is going: queued, in_progress, ready or failed. Once ready, each video has after_steps, the steps it offers next with their prices in credits. Each ready video's download_url is made new in every answer and works for the next hour: its download_expires_in_seconds is the seconds it has left as that answer is made. For a later download, call get_video again for a new link. Free. |
list_videos | The team's UGC videos, newest first. Each ready video's download_url is made new in every answer and works for the next hour: its download_expires_in_seconds is the seconds it has left as that answer is made. For a later download, call get_video again for a new link. Free. |
list_presenters | The presenters a video can be made with, by id: ours and the ones the team described. Free. |
describe_presenter | Draw a presenter of the team's own from a description of how they look, as the app's Describe a presenter does: an adult, invented, not a real person or a character. Free, and counted against the team's 5 drawings a day; a description refused, or a drawing that fails, gives its drawing back. Answers an id at once: call get_described with it every few seconds until its status is ready or refused. |
get_described | How a drawing from describe_presenter is going: checking, drawing, ready with its 3 pictures, each by its number with a link that works for an hour, and the voice the description said if it said one, or refused with why. The person chooses one of the pictures, which keep_presenter keeps. Free. |
keep_presenter | Keep one picture of a drawing as one of the team's presenters, with the voice the person chose for them. Answers the presenter's id, for presenters in the other tools. Free. |
set_presenter_voice | Say or change the voice one of the team's described presenters speaks in, which make_video asks for before an Arabic video with a presenter nobody has given one: the person's choice, which a picture does not tell. Free. |
change_video | Change one finished video as the person asks, made again into a new video beside it, as the app's Change does. This spends the team's credits: the price get_video lists in after_steps as change_video, refunded if it fails. Only for a video whose after_steps offers it. What to change is held to the app's checks: no price, and nothing that gives the presenter a past with the product. Answers an id at once: call get_video with it about once a minute until its status is ready or failed. |
make_longer | Make one finished video longer, its take carried on with new lines written from the product page, into a new video beside it, as the app's Make this video 30 seconds does. This spends the team's credits: the price get_video lists in after_steps as make_longer, refunded if it fails. Only for a video whose after_steps offers it. Answers an id at once: call get_video with it about once a minute until its status is ready or failed. |
another_language | Make one finished video again in another language, its own script and presenter, into a new video beside it, as the app's Another language does. This spends the team's credits: the price get_video lists in after_steps as another_language, or its voiced price for a voiced language, refunded if it fails. Only in a language that after_steps lists for it. Answers an id at once: call get_video with it about once a minute until its status is ready or failed. |
other_sizes | Lay one finished video out in another size, the same video cut for that frame, as the app's Other sizes does. Free. Only for a video whose after_steps lists other_sizes, in a size it lists. Answers an id at once: call get_video with it every few seconds until its status is ready or failed. The same size asked again answers the one already made or being made. |
list_ads | The Meta Ad Library ads the team's store already holds, to build a video like one with ad: its Competitors, the Winning ads of its kind and the library links it pasted, each with its id, advertiser, title, days running and whether it is a video, which ad needs. Free, and nothing is looked up. |
In a client that draws MCP Apps, ChatGPT among them, read_product, list_presenters, get_video and list_videos show their answers in our own display: the product's photos, the presenters' pictures, and each video with its player and its download. Its pictures come from our own host, each by a link that is its own pass for an hour.
Connect in claude.ai or ChatGPT
claude.ai and ChatGPT connect to the MCP server by signing you in to Say It Again, with no key to copy.
Claude asks you to confirm, then Say It Again asks you to allow it.
In ChatGPT, or in Claude by hand:
- Add a connector with the address
https://app.sayitagain.ai/mcp. - When it asks, sign in to Say It Again as your team's owner.
- Check the app's name, where it returns to and your team, and press Allow.
Your team needs a paid plan, as for a key. The app then makes videos with your team's credits, with a key's limits. It is listed in Settings, under API, as a connected app, where you disconnect it, and stays connected for 180 days, after which it asks you to allow it again. Changing your password or turning on two-factor authentication disconnects every app.
An app that signs in this way can return only to chatgpt.com, claude.ai, claude.com, or to a program on your own computer.
Every token it gets is for the MCP server at https://app.sayitagain.ai/mcp and nothing else. An app may send resource with that address when it signs in and asks for tokens; any other address is refused.
In ChatGPT, Say It Again makes no ads for the kinds of product ChatGPT's plugin guidelines bar, among them tobacco and nicotine products, weapons, drugs, CBD over the legal THC limit and adult products. ChatGPT is told them when it connects. A tool called for such a product, or with a description or lines that show or say one, is refused before anything is spent, with product_not_allowed, and one that could not be checked with product_not_checked.
Something here wrong or missing? Write to support@sayitagain.ai.