In this guide
HTTP · version 2
Your studio’s API.
Connect your tools to ClipCentral: sources, clips, captions, exports and publishing, with your workspace’s permissions.
- Base URL
https://www.clipcentral.ai/api/integrations/v2
Start with a read
Use a token obtained through OAuth for this variable. The complete flow is described below.
curl --fail-with-body \
'https://www.clipcentral.ai/api/integrations/v2/identity' \
-H "Authorization: Bearer $CLIPCENTRAL_ACCESS_TOKEN"1. Discover OAuth
Use Authorization Code with PKCE S256. The API does not accept Studio cookies, a static API key or a token intended for the MCP server. Its OAuth resource is exactly the base URL, without a trailing slash.
Read the public metadata before connecting a user. Verify the expected resource and issuer, then use the advertised endpoints. If discovery is unavailable, the integration must not fall back to Studio session routes.
const issuer = "https://www.clipcentral.ai";
const resource = issuer + "/api/integrations/v2";
async function readJson(url: string, init?: RequestInit) {
const response = await fetch(url, { ...init, redirect: "error" });
if (!response.ok) throw new Error("HTTP " + response.status);
return response.json();
}
const protectedResource = await readJson(
issuer + "/.well-known/oauth-protected-resource/api/integrations/v2",
);
if (protectedResource.resource !== resource ||
!protectedResource.authorization_servers?.includes(issuer)) {
throw new Error("Unexpected OAuth resource or issuer");
}
const oauth = await readJson(issuer + "/.well-known/oauth-authorization-server");
if (oauth.issuer !== issuer) throw new Error("Unexpected OAuth issuer");2. Register your application
Register the client once, then keep its client_id. This example uses a public client with token_endpoint_auth_method: none; it has no client secret. Register an exact HTTPS callback URL. HTTP is accepted only on loopback for a local client.
Replace the example callback URL with your application’s URL. The oauth, issuer and resource variables come from the previous step. These Node.js examples use native fetch and node:crypto.
const redirectUri = "https://your-app.example/oauth/clipcentral/callback";
const client = await readJson(oauth.registration_endpoint, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
client_name: "My ClipCentral integration",
redirect_uris: [redirectUri],
token_endpoint_auth_method: "none",
grant_types: ["authorization_code", "refresh_token"],
response_types: ["code"],
}),
});
const clientId: string = client.client_id;3. Request consent
For each connection, generate a random state and code_verifier, then its SHA-256 challenge. Keep these values, the issuer, client_id, resource and redirect_uri in the user’s secure session before redirecting. Bind this request to the user, limit its lifetime and consume it once.
Open the authorization URL in the browser. The user chooses a workspace and the permissions to grant. Start with clipcentral:read offline_access, then request only the scopes your integration needs.
import { createHash, randomBytes } from "node:crypto";
const pending = {
state: randomBytes(32).toString("base64url"),
verifier: randomBytes(32).toString("base64url"),
issuer, resource, clientId, redirectUri,
};
const challenge = createHash("sha256")
.update(pending.verifier).digest("base64url");
const authorizationUrl = new URL(oauth.authorization_endpoint);
authorizationUrl.search = new URLSearchParams({
client_id: pending.clientId,
redirect_uri: pending.redirectUri,
response_type: "code",
scope: "clipcentral:read offline_access",
resource: pending.resource,
state: pending.state,
code_challenge: challenge,
code_challenge_method: "S256",
}).toString();
// Persist pending in this user's secure session, then redirect
// the browser to authorizationUrl. Never log pending.verifier.4. Verify the callback and obtain a token
In your application’s callback handler, retrieve the pending request from the same user’s session. Check state and iss before exchanging the code. Reject missing, expired or already consumed requests and repeated OAuth parameters. Do not log the code or verifier.
callbackUrl is the URL received by your handler; pending is the session value created in the previous step. Session and token storage belong to your application. Consume the pending request after validation, then store the token response on the server without displaying it in the browser.
const callback = new URL(callbackUrl);
for (const key of ["state", "iss", "code", "error"]) {
if (callback.searchParams.getAll(key).length > 1) {
throw new Error("Duplicate OAuth parameter");
}
}
if (callback.searchParams.get("state") !== pending.state ||
callback.searchParams.get("iss") !== pending.issuer) {
throw new Error("Invalid OAuth callback");
}
if (callback.searchParams.has("error")) {
throw new Error("Authorization was not granted");
}
const code = callback.searchParams.get("code");
if (!code) throw new Error("Missing authorization code");
// Atomically consume the validated pending request in your session.
const tokens = await readJson(oauth.token_endpoint, {
method: "POST",
headers: { "Content-Type": "application/x-www-form-urlencoded" },
body: new URLSearchParams({
grant_type: "authorization_code",
client_id: pending.clientId,
redirect_uri: pending.redirectUri,
resource: pending.resource,
code_verifier: pending.verifier,
code,
}),
});
// Store tokens.access_token and tokens.refresh_token securely.
// Use tokens.expires_in to track expiration; do not log tokens.5. Call the API
Send the token in Authorization: Bearer. GET /identity lets you check the current user, workspace, role and scopes. GET /operations returns the catalog and your connection’s permissions; GET /openapi.json provides OpenAPI 3.1 after authentication.
Operations use POST /operations/{name} with a JSON object. Do not put operation arguments in query parameters on this route. GET aliases for clips, sources, jobs, credits and workspace accept their schema’s parameters. Pagination varies by operation: check its reference.
curl --fail-with-body \
'https://www.clipcentral.ai/api/integrations/v2/operations/workspace_status' \
-H "Authorization: Bearer $CLIPCENTRAL_ACCESS_TOKEN" \
-H 'Content-Type: application/json' \
--data '{}'6. Refresh and revoke access
Access tokens expire after one hour. offline_access enables a refresh token with an absolute 30-day lifetime. Refresh with the same client and resource. Immediately store the new refresh token and atomically replace the old one.
Serialize refreshes for a connection: reusing an old refresh token revokes its family. If a refresh response is lost, do not blindly repeat the same refresh. Revocation or an incompatible role change can also invalidate the connection; reconnect the user when needed.
const nextTokens = await readJson(oauth.token_endpoint, {
method: "POST",
headers: { "Content-Type": "application/x-www-form-urlencoded" },
body: new URLSearchParams({
grant_type: "refresh_token",
client_id: clientId,
resource,
refresh_token: storedRefreshToken,
}),
});
// Atomically replace the stored token pair with nextTokens.
// The user can revoke the connection at /connections.Scopes, roles and confirmations
clipcentral:read is required. Token permissions are combined with the current workspace role; they do not replace it. Membership and role are checked again on each request. A reviewer with a write scope does not become an editor.
Variants may require different permissions: a status check or quote may remain read-only, while starting work requires process. Check authorizationVariants and the exact schema. Business confirmations, expected revisions and disclosed costs remain mandatory when required.
| Scope | Purpose |
|---|---|
clipcentral:read | Read workspace data |
offline_access | Refresh access, with no additional business permission |
clipcentral:write | Edit content and projects |
clipcentral:process | Start processing, AI and exports |
clipcentral:publish | Facebook and YouTube publishing |
clipcentral:messages:read | Read private Messenger messages |
clipcentral:comments:write | Publish confirmed Facebook comments |
clipcentral:social:write | Social actions, synchronization and moderation |
clipcentral:review | Internal review and transcript validation |
clipcentral:billing:write | Billing for owner/admin |
Upload a source
With direct storage, start with source_upload_init. Use an idempotency key saved before the call. Then PUT the bytes to uploadUrl with uploadHeaders, without a ClipCentral Bearer token. Finish with source_upload_complete, the returned sourceId and a new key. Admission validates the media before preparation.
This example describes a 10 MiB MP4: replace its name, size and type with the actual file’s values. Check the reference for each step’s scopes, confirmations and required fields; business limits and transcription costs still apply.
curl --fail-with-body \
'https://www.clipcentral.ai/api/integrations/v2/operations/source_upload_init' \
-H "Authorization: Bearer $CLIPCENTRAL_ACCESS_TOKEN" \
-H 'Content-Type: application/json' \
-H "Idempotency-Key: $SAVED_UPLOAD_REQUEST_KEY" \
--data '{"name":"interview.mp4","sizeBytes":10485760,"contentType":"video/mp4","confirm":true}'Track processing and exports
An enqueue operation may create asynchronous work. Keep the returned identifiers and read jobs with jobs_list or job_show; retrieve results using the relevant domain operation. Poll with backoff, respect quotas and display the actual state without treating admission as a completed file.
For clips, discover capabilities with clip_edit_capabilities, then use the appropriate preparation and finalization tools. Final delivery keeps the pipeline’s validations: cover, captioned vertical export and Facebook version. Download an existing export without starting a new render.
Available processing depends on the features and connections enabled in the workspace. editor_export_video is unavailable in this API v2 release; that does not describe Studio capabilities. The API does not provide a general outgoing webhook for job completion.
Avoid duplicate actions
Operations with write, enqueue or external effects require Idempotency-Key. Choose a key of 8–128 characters using A–Z, a–z, 0–9, dot, hyphen, underscore and colon, then save it before the call. Reusing a key with different arguments returns 409. A completed response replay includes Idempotency-Replayed: true.
A network error, interruption or operation_outcome_unknown does not prove that no effects occurred. The receipt may remain pending: check jobs, resources or publications before deciding on a new action. Do not change keys or reconnect a grant to blindly repeat publishing or billing.
A key is scoped to the workspace, client and grant. Replaying a completed response is limited to 30 days; an expired key does not execute a new action. Responses larger than 1 MiB are not replayable: read the resulting resource. This mechanism does not guarantee an exactly-once transaction at an external provider.
Handle errors
Ordinary HTTP errors use the shape below. Keep requestId and the public code for diagnostics without recording tokens or signed URLs. Messages and detailed response fields depend on the operation.
400: correct arguments. 401: check the token and audience. 402: check required credits. 403: check scopes, role and membership. 404: check the resource. 409: inspect conflicts, revisions or the idempotency receipt. 413/415: check media size and type. 422: review business validation. 429: respect Retry-After before an eligible retry. 5xx: inspect actual state before any new mutation.
{
"apiVersion": "2",
"requestId": "<request-id>",
"error": {
"code": "operation_outcome_unknown",
"message": "<public error message>"
}
}Limits and private data
Limits are 120 requests per minute per grant and 120 mutations per minute per workspace, shared across all connections to that workspace. Provider quotas and business credits apply in addition.
JSON is limited to 2 MiB, 64 levels of depth and 200,000 values/containers. Numbers must be finite. Unknown or repeated parameters and contradictory identifiers on GET aliases are rejected. Use POST for complex objects or arrays.
Media is private and temporary URLs may expire. GET /media supports Range and may return a 307 redirect to storage. Follow that URL without forwarding the Bearer token to the other host. An expired link does not authorize a new render; request fresh access to the existing media.
Credits and publishing
Calls use the workspace balance and each operation’s cost rules. Read credits_balance and available quotes before starting processing. A process scope does not make operations free. Refunds apply to eligible processing under its contract.
Billing actions require owner/admin and clipcentral:billing:write. A subscription change requires acknowledgeImmediateProration: true; proration may be charged immediately. Checkout and portal operations return Stripe links for interactive completion.
Preview publishing, then obtain the required confirmation before a public action. Facebook and YouTube use their respective connections and permissions. Other destinations retain the export or management capabilities described by their operation; universal publishing is not implied.
View pricing and creditsHTTP API or MCP agent?
Choose the HTTP API to call ClipCentral from your application, backend or automation. Choose the MCP server for a compatible assistant. They use distinct OAuth audiences: a /mcp token does not authenticate /api/integrations/v2, or vice versa.
This guide’s examples use HTTP directly and do not require an SDK package. Your application must handle OAuth with PKCE, secure token storage and retries according to the idempotency rules.
Connect an MCP agent