Error Handling
All errors return a JSON body with a machine-readable code. Concurrency limits scale with your plan.
Failed requests return a non-2xx status and a JSON body with success: false.
{
"success": false,
"code": "INVALID_INPUT",
"error": "videoUrl is required"
}Prop
Type
Error codes
Both BAD_REQUEST and INVALID_INPUT return status 400 but mean different things. BAD_REQUEST is a schema problem: a missing required field or an unrecognized enum value. INVALID_INPUT means the body looked fine but the content was wrong, like a URL that points nowhere or a video without captions.
Prop
Type
Concurrency
The API limits how many requests you can have in flight at the same time, scaled by your plan. This caps parallelism, not total throughput. Fire requests as fast as you want; keep the number running concurrently under your limit.
Your plan is the highest credit pack you have ever purchased. Buying a smaller pack for extra credits never lowers it. Unauthenticated requests and accounts with no pack use the Free limit.
| Plan | Concurrency (in flight) |
|---|---|
| Free / no pack | 5 |
| Starter | 15 |
| Builder | 60 |
| Pro | 150 |
| Scale | 300 |
Every response carries your current concurrency state:
| Header | Description |
|---|---|
X-Concurrency-Limit | Max simultaneous in-flight requests for your plan |
X-Concurrency-Remaining | In-flight slots still available right now |
Going over returns 429 CONCURRENCY_LIMITED. It clears as soon as one of your in-flight requests finishes, so lower your parallelism rather than waiting on a clock.
Versioning
The API is versioned in the URL path (/v1/). Breaking changes like removed fields, changed response shapes, or altered error codes will always move to a new version (/v2/) with advance notice. We may add new optional fields or new error codes to the current version without notice. Handle unknown error codes in your default or else branch so your code stays forward-compatible.
Handling errors
The SDKs throw a typed StophyError carrying status, code, the
message, and requestId. Branch on code and let unknown codes fall through to the
default case.
import { Stophy, StophyError } from "stophy";
const stophy = new Stophy({ apiKey: process.env.STOPHY_API_KEY! });
try {
const { data } = await stophy.video({
type: "details",
videoUrl: "https://www.youtube.com/watch?v=D7liwdjvhWc",
});
console.log(data);
} catch (err) {
if (!(err instanceof StophyError)) throw err;
switch (err.code) {
case "INSUFFICIENT_CREDITS":
// top up from dashboard
break;
case "CONCURRENCY_LIMITED":
// too many requests in flight; retry once one finishes
break;
case "UNAUTHORIZED":
// check your API key
break;
default:
console.error(err.status, err.code, err.message);
}
}import os
from stophy import Stophy, StophyError
stophy = Stophy(os.environ["STOPHY_API_KEY"])
try:
result = stophy.video(
type="details",
video_url="https://www.youtube.com/watch?v=D7liwdjvhWc",
)
print(result["data"])
except StophyError as err:
if err.code == "INSUFFICIENT_CREDITS":
pass # top up from dashboard
elif err.code == "CONCURRENCY_LIMITED":
pass # too many requests in flight; retry once one finishes
elif err.code == "UNAUTHORIZED":
pass # check your API key
else:
print(err.status, err.code, err)For raw HTTP, read code off the JSON body when the response is non-2xx.