@convertrilo/sdk 0.0.10 → 0.0.12
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +34 -0
- package/dist/src/index.d.ts +30 -2
- package/dist/src/index.js +8 -2
- package/dist/src/types.d.ts +62 -5
- package/docs/API-INTEGRATION-GUIDE.md +37 -0
- package/examples/idempotency.ts +60 -0
- package/examples/webhook-receiver-hmac.ts +54 -0
- package/openapi.yaml +90 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -33,6 +33,8 @@ The `examples/` directory contains starter scripts for the main integration path
|
|
|
33
33
|
- `node-url-to-s3.ts` - encode a public URL and upload the result to S3/S3-compatible storage
|
|
34
34
|
- `google-drive-byo-token.ts` - upload output to Google Drive using customer OAuth tokens from your app
|
|
35
35
|
- `folder-ingest-s3.ts` - queue one encode job per video in an S3 prefix
|
|
36
|
+
- `idempotency.ts` - safely retry `createJob` and `createJobsBulk`
|
|
37
|
+
- `webhook-receiver-hmac.ts` - verify managed webhook HMAC signatures from a Node receiver
|
|
36
38
|
|
|
37
39
|
Local SDK smoke tests use `.env`, but published SDK users should provide credentials through their
|
|
38
40
|
own server environment. Do not put Convertrilo API keys or customer storage tokens in frontend code.
|
|
@@ -41,6 +43,38 @@ For a complete server-to-server walkthrough covering URL, S3, folder ingest, Goo
|
|
|
41
43
|
OAuth tokens, polling, and webhooks, see
|
|
42
44
|
[`docs/API-INTEGRATION-GUIDE.md`](docs/API-INTEGRATION-GUIDE.md).
|
|
43
45
|
|
|
46
|
+
## Idempotent Job Creation
|
|
47
|
+
|
|
48
|
+
Use an idempotency key when retrying create calls from your backend. Reusing the same key with the
|
|
49
|
+
same body returns the original response instead of creating duplicate jobs.
|
|
50
|
+
|
|
51
|
+
```ts
|
|
52
|
+
const job = await client.createJob({
|
|
53
|
+
externalId: "upload-123",
|
|
54
|
+
metadata: { customerId: "cus_123" },
|
|
55
|
+
codec: "h264",
|
|
56
|
+
resolution: "1080p",
|
|
57
|
+
fps: 30,
|
|
58
|
+
}, {
|
|
59
|
+
idempotencyKey: "job-upload-123",
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
const batch = await client.createJobsBulk({
|
|
63
|
+
jobs: [
|
|
64
|
+
{
|
|
65
|
+
externalId: "batch-42:clip-1",
|
|
66
|
+
codec: "h264",
|
|
67
|
+
resolution: "1080p",
|
|
68
|
+
fps: 30,
|
|
69
|
+
sourceS3: { bucket: "source", key: "clip-1.mp4" },
|
|
70
|
+
},
|
|
71
|
+
],
|
|
72
|
+
settings: { confirm: true },
|
|
73
|
+
}, {
|
|
74
|
+
idempotencyKey: "bulk-batch-42",
|
|
75
|
+
});
|
|
76
|
+
```
|
|
77
|
+
|
|
44
78
|
## URL Source To CDN Output
|
|
45
79
|
|
|
46
80
|
```ts
|
package/dist/src/index.d.ts
CHANGED
|
@@ -35,8 +35,13 @@ export declare class ConvertriloClient {
|
|
|
35
35
|
reserveTokens(body: paths["/tokens/reserve"]["post"]["requestBody"]["content"]["application/json"]): Promise<unknown>;
|
|
36
36
|
releaseTokens(body: paths["/tokens/release"]["post"]["requestBody"]["content"]["application/json"]): Promise<unknown>;
|
|
37
37
|
deductTokens(body: paths["/tokens/deduct"]["post"]["requestBody"]["content"]["application/json"]): Promise<unknown>;
|
|
38
|
-
createJob(body: paths["/jobs"]["post"]["requestBody"]["content"]["application/json"]): Promise<{
|
|
38
|
+
createJob(body: paths["/jobs"]["post"]["requestBody"]["content"]["application/json"], options?: RequestOptions): Promise<{
|
|
39
39
|
jobId: string;
|
|
40
|
+
externalId?: string | null;
|
|
41
|
+
metadata?: {
|
|
42
|
+
[key: string]: unknown;
|
|
43
|
+
} | null;
|
|
44
|
+
status?: string;
|
|
40
45
|
upload: {
|
|
41
46
|
url?: string;
|
|
42
47
|
key?: string;
|
|
@@ -47,6 +52,8 @@ export declare class ConvertriloClient {
|
|
|
47
52
|
};
|
|
48
53
|
estimate?: {
|
|
49
54
|
neu?: number;
|
|
55
|
+
totalNeu?: number;
|
|
56
|
+
reserved?: number;
|
|
50
57
|
};
|
|
51
58
|
}>;
|
|
52
59
|
probeDuration(id: string): Promise<{
|
|
@@ -68,19 +75,40 @@ export declare class ConvertriloClient {
|
|
|
68
75
|
cancelJob(id: string): Promise<unknown>;
|
|
69
76
|
jobStatus(id: string): Promise<{
|
|
70
77
|
id?: string;
|
|
78
|
+
externalId?: string | null;
|
|
79
|
+
metadata?: {
|
|
80
|
+
[key: string]: unknown;
|
|
81
|
+
} | null;
|
|
71
82
|
status?: string;
|
|
83
|
+
ingest?: string | null;
|
|
84
|
+
output?: {
|
|
85
|
+
key?: string;
|
|
86
|
+
publicUrl?: string;
|
|
87
|
+
} | null;
|
|
72
88
|
downloadUrl?: string | null;
|
|
73
89
|
encoder?: string | null;
|
|
74
90
|
pct?: number | null;
|
|
91
|
+
createdAt?: string;
|
|
92
|
+
startedAt?: string | null;
|
|
93
|
+
finishedAt?: string | null;
|
|
94
|
+
failureMessage?: string | null;
|
|
75
95
|
}>;
|
|
76
|
-
createJobsBulk(body: paths["/jobs/bulk"]["post"]["requestBody"]["content"]["application/json"]): Promise<{
|
|
96
|
+
createJobsBulk(body: paths["/jobs/bulk"]["post"]["requestBody"]["content"]["application/json"], options?: RequestOptions): Promise<{
|
|
77
97
|
totalJobs?: number;
|
|
78
98
|
totalEstimatedNeu?: number;
|
|
99
|
+
reserved?: number;
|
|
79
100
|
jobs?: {
|
|
80
101
|
index?: number;
|
|
81
102
|
jobId?: string;
|
|
103
|
+
externalId?: string | null;
|
|
104
|
+
metadata?: {
|
|
105
|
+
[key: string]: unknown;
|
|
106
|
+
} | null;
|
|
82
107
|
status?: string;
|
|
83
108
|
error?: string;
|
|
109
|
+
estimate?: {
|
|
110
|
+
neu?: number;
|
|
111
|
+
};
|
|
84
112
|
}[];
|
|
85
113
|
}>;
|
|
86
114
|
bulkStatus(ids: string[]): Promise<{
|
package/dist/src/index.js
CHANGED
|
@@ -68,10 +68,13 @@ export class ConvertriloClient {
|
|
|
68
68
|
});
|
|
69
69
|
}
|
|
70
70
|
// Jobs
|
|
71
|
-
async createJob(body) {
|
|
71
|
+
async createJob(body, options = {}) {
|
|
72
72
|
return this.request(`/jobs`, {
|
|
73
73
|
method: "POST",
|
|
74
74
|
body: JSON.stringify(body),
|
|
75
|
+
headers: options.idempotencyKey
|
|
76
|
+
? { "Idempotency-Key": options.idempotencyKey }
|
|
77
|
+
: undefined,
|
|
75
78
|
});
|
|
76
79
|
}
|
|
77
80
|
async probeDuration(id) {
|
|
@@ -96,10 +99,13 @@ export class ConvertriloClient {
|
|
|
96
99
|
return this.request(`/jobs/${id}/status`);
|
|
97
100
|
}
|
|
98
101
|
// Bulk Jobs
|
|
99
|
-
async createJobsBulk(body) {
|
|
102
|
+
async createJobsBulk(body, options = {}) {
|
|
100
103
|
return this.request(`/jobs/bulk`, {
|
|
101
104
|
method: "POST",
|
|
102
105
|
body: JSON.stringify(body),
|
|
106
|
+
headers: options.idempotencyKey
|
|
107
|
+
? { "Idempotency-Key": options.idempotencyKey }
|
|
108
|
+
: undefined,
|
|
103
109
|
});
|
|
104
110
|
}
|
|
105
111
|
async bulkStatus(ids) {
|
package/dist/src/types.d.ts
CHANGED
|
@@ -204,12 +204,15 @@ export interface paths {
|
|
|
204
204
|
put?: never;
|
|
205
205
|
/**
|
|
206
206
|
* Create a new encode job
|
|
207
|
-
* @description Create a job for upload ingest (returns a presigned PUT) or direct URL/S3 ingest.
|
|
207
|
+
* @description Create a job for upload ingest (returns a presigned PUT) or direct URL/S3 ingest. Send `Idempotency-Key` when retrying from your backend to avoid duplicate jobs.
|
|
208
208
|
*/
|
|
209
209
|
post: {
|
|
210
210
|
parameters: {
|
|
211
211
|
query?: never;
|
|
212
|
-
header?:
|
|
212
|
+
header?: {
|
|
213
|
+
/** @description Optional key for safely retrying job creation requests. Reusing the same key with the same request body replays the original response; reusing it with a different body returns 409. */
|
|
214
|
+
"Idempotency-Key"?: components["parameters"]["IdempotencyKey"];
|
|
215
|
+
};
|
|
213
216
|
path?: never;
|
|
214
217
|
cookie?: never;
|
|
215
218
|
};
|
|
@@ -773,11 +776,17 @@ export interface paths {
|
|
|
773
776
|
};
|
|
774
777
|
get?: never;
|
|
775
778
|
put?: never;
|
|
776
|
-
/**
|
|
779
|
+
/**
|
|
780
|
+
* Bulk create jobs
|
|
781
|
+
* @description Send `Idempotency-Key` when retrying from your backend to avoid duplicate bulk batches, duplicate token reservations, or duplicate queue entries.
|
|
782
|
+
*/
|
|
777
783
|
post: {
|
|
778
784
|
parameters: {
|
|
779
785
|
query?: never;
|
|
780
|
-
header?:
|
|
786
|
+
header?: {
|
|
787
|
+
/** @description Optional key for safely retrying job creation requests. Reusing the same key with the same request body replays the original response; reusing it with a different body returns 409. */
|
|
788
|
+
"Idempotency-Key"?: components["parameters"]["IdempotencyKey"];
|
|
789
|
+
};
|
|
781
790
|
path?: never;
|
|
782
791
|
cookie?: never;
|
|
783
792
|
};
|
|
@@ -1505,6 +1514,12 @@ export interface components {
|
|
|
1505
1514
|
amount: number;
|
|
1506
1515
|
};
|
|
1507
1516
|
JobCreateRequest: {
|
|
1517
|
+
/** @description Your stable job identifier for reconciliation in status responses and webhooks. */
|
|
1518
|
+
externalId?: string;
|
|
1519
|
+
/** @description Integration-owned JSON object returned in status responses and webhooks. */
|
|
1520
|
+
metadata?: {
|
|
1521
|
+
[key: string]: unknown;
|
|
1522
|
+
};
|
|
1508
1523
|
/** @enum {string} */
|
|
1509
1524
|
codec: "h264" | "h265" | "av1";
|
|
1510
1525
|
/** @enum {string} */
|
|
@@ -1559,6 +1574,12 @@ export interface components {
|
|
|
1559
1574
|
};
|
|
1560
1575
|
JobCreateResponse: {
|
|
1561
1576
|
jobId: string;
|
|
1577
|
+
externalId?: string | null;
|
|
1578
|
+
metadata?: {
|
|
1579
|
+
[key: string]: unknown;
|
|
1580
|
+
} | null;
|
|
1581
|
+
/** @description Present when the job is auto-confirmed and queued. */
|
|
1582
|
+
status?: string;
|
|
1562
1583
|
upload: {
|
|
1563
1584
|
url?: string;
|
|
1564
1585
|
key?: string;
|
|
@@ -1569,6 +1590,8 @@ export interface components {
|
|
|
1569
1590
|
};
|
|
1570
1591
|
estimate?: {
|
|
1571
1592
|
neu?: number;
|
|
1593
|
+
totalNeu?: number;
|
|
1594
|
+
reserved?: number;
|
|
1572
1595
|
};
|
|
1573
1596
|
};
|
|
1574
1597
|
ProbeDurationResponse: {
|
|
@@ -1619,13 +1642,39 @@ export interface components {
|
|
|
1619
1642
|
};
|
|
1620
1643
|
StatusResponse: {
|
|
1621
1644
|
id?: string;
|
|
1645
|
+
externalId?: string | null;
|
|
1646
|
+
metadata?: {
|
|
1647
|
+
[key: string]: unknown;
|
|
1648
|
+
} | null;
|
|
1622
1649
|
status?: string;
|
|
1650
|
+
ingest?: string | null;
|
|
1651
|
+
output?: {
|
|
1652
|
+
key?: string;
|
|
1653
|
+
publicUrl?: string;
|
|
1654
|
+
} | null;
|
|
1623
1655
|
downloadUrl?: string | null;
|
|
1624
1656
|
encoder?: string | null;
|
|
1625
1657
|
pct?: number | null;
|
|
1658
|
+
/** Format: date-time */
|
|
1659
|
+
createdAt?: string;
|
|
1660
|
+
/** Format: date-time */
|
|
1661
|
+
startedAt?: string | null;
|
|
1662
|
+
/** Format: date-time */
|
|
1663
|
+
finishedAt?: string | null;
|
|
1664
|
+
failureMessage?: string | null;
|
|
1626
1665
|
};
|
|
1627
1666
|
ErrorResponse: {
|
|
1628
|
-
|
|
1667
|
+
/**
|
|
1668
|
+
* @description Stable machine-readable error code. Branch on this instead of parsing `message`.
|
|
1669
|
+
* @enum {string}
|
|
1670
|
+
*/
|
|
1671
|
+
code: "invalid_api_key" | "api_key_expired" | "user_not_found" | "account_banned" | "missing_required_scope" | "invalid_request" | "invalid_body" | "invalid_url" | "url_ingest_disabled" | "url_host_blocked" | "signed_url_required" | "host_not_resolvable" | "private_ip_blocked" | "missing_content_length" | "file_too_large" | "unsupported_content_type" | "probe_failed" | "insufficient_tokens" | "idempotency_conflict" | "idempotency_in_progress" | "not_found" | "forbidden" | "deprecated_dropbox" | "google_drive_not_connected" | "dropbox_not_connected" | "no_video_files_found" | "webhook_limit_exceeded" | "webhook_not_found" | "no_updates_provided" | "output_not_available";
|
|
1672
|
+
message: string;
|
|
1673
|
+
/** @description Validation details for `invalid_body`. */
|
|
1674
|
+
issues?: unknown;
|
|
1675
|
+
requiredScope?: string;
|
|
1676
|
+
required?: number;
|
|
1677
|
+
available?: number;
|
|
1629
1678
|
};
|
|
1630
1679
|
BulkCreateRequest: {
|
|
1631
1680
|
jobs: components["schemas"]["JobCreateRequest"][];
|
|
@@ -1648,12 +1697,20 @@ export interface components {
|
|
|
1648
1697
|
BulkCreateResponse: {
|
|
1649
1698
|
totalJobs?: number;
|
|
1650
1699
|
totalEstimatedNeu?: number;
|
|
1700
|
+
reserved?: number;
|
|
1651
1701
|
jobs?: {
|
|
1652
1702
|
index?: number;
|
|
1653
1703
|
/** Format: uuid */
|
|
1654
1704
|
jobId?: string;
|
|
1705
|
+
externalId?: string | null;
|
|
1706
|
+
metadata?: {
|
|
1707
|
+
[key: string]: unknown;
|
|
1708
|
+
} | null;
|
|
1655
1709
|
status?: string;
|
|
1656
1710
|
error?: string;
|
|
1711
|
+
estimate?: {
|
|
1712
|
+
neu?: number;
|
|
1713
|
+
};
|
|
1657
1714
|
}[];
|
|
1658
1715
|
};
|
|
1659
1716
|
BulkStatusResponse: {
|
|
@@ -46,6 +46,43 @@ const client = new ConvertriloClient({
|
|
|
46
46
|
|
|
47
47
|
SDK source and examples: https://github.com/serkandrgn/convertrilo-js
|
|
48
48
|
|
|
49
|
+
## Idempotency
|
|
50
|
+
|
|
51
|
+
When your backend retries `POST /jobs`, `POST /jobs/bulk`, `POST /ondemand/encode`, or
|
|
52
|
+
`POST /ondemand/ingest/folder`, send an idempotency key. The API replays the original response for
|
|
53
|
+
the same key and body, and returns `409` if the key is reused with a different body.
|
|
54
|
+
|
|
55
|
+
```ts
|
|
56
|
+
const job = await client.createJob({
|
|
57
|
+
externalId: "upload-123",
|
|
58
|
+
metadata: { customerId: "cus_123" },
|
|
59
|
+
codec: "h264",
|
|
60
|
+
resolution: "1080p",
|
|
61
|
+
fps: 30,
|
|
62
|
+
}, {
|
|
63
|
+
idempotencyKey: "job-upload-123",
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
const batch = await client.createJobsBulk({
|
|
67
|
+
jobs: [
|
|
68
|
+
{
|
|
69
|
+
externalId: "batch-42:clip-1",
|
|
70
|
+
codec: "h264",
|
|
71
|
+
resolution: "1080p",
|
|
72
|
+
fps: 30,
|
|
73
|
+
sourceS3: { bucket: "source", key: "clip-1.mp4" },
|
|
74
|
+
},
|
|
75
|
+
],
|
|
76
|
+
settings: { confirm: true },
|
|
77
|
+
}, {
|
|
78
|
+
idempotencyKey: "bulk-batch-42",
|
|
79
|
+
});
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
API errors include a stable `code` field plus a human-readable `message`. Branch on
|
|
83
|
+
`code` in your backend, for example `idempotency_conflict`, `insufficient_tokens`,
|
|
84
|
+
or `missing_required_scope`.
|
|
85
|
+
|
|
49
86
|
## Flow 1: URL Source To CDN Output
|
|
50
87
|
|
|
51
88
|
Use this when the source video is already available over HTTP(S), and you want Convertrilo to return a signed CDN download URL.
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import { ConvertriloClient } from "@convertrilo/sdk";
|
|
2
|
+
|
|
3
|
+
const apiKey = process.env.CONVERTRILO_API_KEY;
|
|
4
|
+
if (!apiKey) throw new Error("Set CONVERTRILO_API_KEY");
|
|
5
|
+
|
|
6
|
+
const client = new ConvertriloClient({
|
|
7
|
+
baseUrl: process.env.CONVERTRILO_API_URL || "https://api.convertrilo.com",
|
|
8
|
+
apiKey,
|
|
9
|
+
});
|
|
10
|
+
|
|
11
|
+
async function main() {
|
|
12
|
+
const externalId = `example-upload-${Date.now()}`;
|
|
13
|
+
|
|
14
|
+
const job = await client.createJob(
|
|
15
|
+
{
|
|
16
|
+
externalId,
|
|
17
|
+
metadata: {
|
|
18
|
+
customerId: "cus_123",
|
|
19
|
+
workflow: "sdk-idempotency-example",
|
|
20
|
+
},
|
|
21
|
+
codec: "h264",
|
|
22
|
+
resolution: "1080p",
|
|
23
|
+
fps: 30,
|
|
24
|
+
},
|
|
25
|
+
{
|
|
26
|
+
idempotencyKey: `job-${externalId}`,
|
|
27
|
+
},
|
|
28
|
+
);
|
|
29
|
+
|
|
30
|
+
console.log(`Created or replayed job ${job.jobId}`);
|
|
31
|
+
|
|
32
|
+
const batch = await client.createJobsBulk(
|
|
33
|
+
{
|
|
34
|
+
jobs: [
|
|
35
|
+
{
|
|
36
|
+
externalId: `${externalId}:clip-1`,
|
|
37
|
+
metadata: { customerId: "cus_123" },
|
|
38
|
+
codec: "h264",
|
|
39
|
+
resolution: "1080p",
|
|
40
|
+
fps: 30,
|
|
41
|
+
sourceS3: {
|
|
42
|
+
bucket: "customer-source-bucket",
|
|
43
|
+
key: "incoming/clip-1.mp4",
|
|
44
|
+
},
|
|
45
|
+
},
|
|
46
|
+
],
|
|
47
|
+
settings: { dryRun: true },
|
|
48
|
+
},
|
|
49
|
+
{
|
|
50
|
+
idempotencyKey: `bulk-${externalId}`,
|
|
51
|
+
},
|
|
52
|
+
);
|
|
53
|
+
|
|
54
|
+
console.log(`Created or replayed bulk response with ${batch.totalJobs} job(s)`);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
main().catch((error) => {
|
|
58
|
+
console.error(error);
|
|
59
|
+
process.exit(1);
|
|
60
|
+
});
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { createHmac, timingSafeEqual } from "node:crypto";
|
|
2
|
+
import http from "node:http";
|
|
3
|
+
|
|
4
|
+
const secret = process.env.CONVERTRILO_WEBHOOK_SECRET;
|
|
5
|
+
if (!secret) throw new Error("Set CONVERTRILO_WEBHOOK_SECRET");
|
|
6
|
+
|
|
7
|
+
function verifySignature(rawBody: Buffer, signature: string) {
|
|
8
|
+
const expected = createHmac("sha256", secret!).update(rawBody).digest("hex");
|
|
9
|
+
const expectedBuffer = Buffer.from(expected, "hex");
|
|
10
|
+
const signatureBuffer = Buffer.from(signature, "hex");
|
|
11
|
+
|
|
12
|
+
return (
|
|
13
|
+
expectedBuffer.length === signatureBuffer.length &&
|
|
14
|
+
timingSafeEqual(expectedBuffer, signatureBuffer)
|
|
15
|
+
);
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
const server = http.createServer((request, response) => {
|
|
19
|
+
if (request.method !== "POST" || request.url !== "/webhooks/convertrilo") {
|
|
20
|
+
response.writeHead(404);
|
|
21
|
+
response.end("Not found");
|
|
22
|
+
return;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
const chunks: Buffer[] = [];
|
|
26
|
+
|
|
27
|
+
request.on("data", (chunk) => chunks.push(Buffer.from(chunk)));
|
|
28
|
+
request.on("end", () => {
|
|
29
|
+
const rawBody = Buffer.concat(chunks);
|
|
30
|
+
const signature = String(request.headers["x-webhook-signature"] || "");
|
|
31
|
+
|
|
32
|
+
if (!signature || !verifySignature(rawBody, signature)) {
|
|
33
|
+
response.writeHead(401);
|
|
34
|
+
response.end("Invalid signature");
|
|
35
|
+
return;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
const payload = JSON.parse(rawBody.toString("utf8"));
|
|
39
|
+
console.log("Verified Convertrilo webhook", {
|
|
40
|
+
event: payload.event,
|
|
41
|
+
jobId: payload.data?.jobId,
|
|
42
|
+
status: payload.data?.status,
|
|
43
|
+
externalId: payload.data?.externalId,
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
response.writeHead(204);
|
|
47
|
+
response.end();
|
|
48
|
+
});
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
const port = Number(process.env.PORT || 3000);
|
|
52
|
+
server.listen(port, () => {
|
|
53
|
+
console.log(`Webhook receiver listening on http://localhost:${port}/webhooks/convertrilo`);
|
|
54
|
+
});
|
package/openapi.yaml
CHANGED
|
@@ -81,6 +81,14 @@ components:
|
|
|
81
81
|
type: object
|
|
82
82
|
required: [codec, resolution, fps]
|
|
83
83
|
properties:
|
|
84
|
+
externalId:
|
|
85
|
+
type: string
|
|
86
|
+
maxLength: 255
|
|
87
|
+
description: Your stable job identifier for reconciliation in status responses and webhooks.
|
|
88
|
+
metadata:
|
|
89
|
+
type: object
|
|
90
|
+
additionalProperties: true
|
|
91
|
+
description: Integration-owned JSON object returned in status responses and webhooks.
|
|
84
92
|
codec:
|
|
85
93
|
type: string
|
|
86
94
|
enum: [h264, h265, av1]
|
|
@@ -136,6 +144,14 @@ components:
|
|
|
136
144
|
required: [jobId, upload, output]
|
|
137
145
|
properties:
|
|
138
146
|
jobId: { type: string }
|
|
147
|
+
externalId: { type: string, nullable: true }
|
|
148
|
+
metadata:
|
|
149
|
+
type: object
|
|
150
|
+
additionalProperties: true
|
|
151
|
+
nullable: true
|
|
152
|
+
status:
|
|
153
|
+
type: string
|
|
154
|
+
description: Present when the job is auto-confirmed and queued.
|
|
139
155
|
upload:
|
|
140
156
|
nullable: true
|
|
141
157
|
type: object
|
|
@@ -151,6 +167,8 @@ components:
|
|
|
151
167
|
type: object
|
|
152
168
|
properties:
|
|
153
169
|
neu: { type: number }
|
|
170
|
+
totalNeu: { type: number }
|
|
171
|
+
reserved: { type: number }
|
|
154
172
|
ProbeDurationResponse:
|
|
155
173
|
type: object
|
|
156
174
|
properties:
|
|
@@ -196,14 +214,70 @@ components:
|
|
|
196
214
|
type: object
|
|
197
215
|
properties:
|
|
198
216
|
id: { type: string }
|
|
217
|
+
externalId: { type: string, nullable: true }
|
|
218
|
+
metadata:
|
|
219
|
+
type: object
|
|
220
|
+
additionalProperties: true
|
|
221
|
+
nullable: true
|
|
199
222
|
status: { type: string }
|
|
223
|
+
ingest: { type: string, nullable: true }
|
|
224
|
+
output:
|
|
225
|
+
nullable: true
|
|
226
|
+
type: object
|
|
227
|
+
properties:
|
|
228
|
+
key: { type: string }
|
|
229
|
+
publicUrl: { type: string }
|
|
200
230
|
downloadUrl: { type: string, nullable: true }
|
|
201
231
|
encoder: { type: string, nullable: true }
|
|
202
232
|
pct: { type: number, nullable: true }
|
|
233
|
+
createdAt: { type: string, format: date-time }
|
|
234
|
+
startedAt: { type: string, format: date-time, nullable: true }
|
|
235
|
+
finishedAt: { type: string, format: date-time, nullable: true }
|
|
236
|
+
failureMessage: { type: string, nullable: true }
|
|
203
237
|
ErrorResponse:
|
|
204
238
|
type: object
|
|
239
|
+
required: [code, message]
|
|
205
240
|
properties:
|
|
241
|
+
code:
|
|
242
|
+
type: string
|
|
243
|
+
description: Stable machine-readable error code. Branch on this instead of parsing `message`.
|
|
244
|
+
enum:
|
|
245
|
+
- invalid_api_key
|
|
246
|
+
- api_key_expired
|
|
247
|
+
- user_not_found
|
|
248
|
+
- account_banned
|
|
249
|
+
- missing_required_scope
|
|
250
|
+
- invalid_request
|
|
251
|
+
- invalid_body
|
|
252
|
+
- invalid_url
|
|
253
|
+
- url_ingest_disabled
|
|
254
|
+
- url_host_blocked
|
|
255
|
+
- signed_url_required
|
|
256
|
+
- host_not_resolvable
|
|
257
|
+
- private_ip_blocked
|
|
258
|
+
- missing_content_length
|
|
259
|
+
- file_too_large
|
|
260
|
+
- unsupported_content_type
|
|
261
|
+
- probe_failed
|
|
262
|
+
- insufficient_tokens
|
|
263
|
+
- idempotency_conflict
|
|
264
|
+
- idempotency_in_progress
|
|
265
|
+
- not_found
|
|
266
|
+
- forbidden
|
|
267
|
+
- deprecated_dropbox
|
|
268
|
+
- google_drive_not_connected
|
|
269
|
+
- dropbox_not_connected
|
|
270
|
+
- no_video_files_found
|
|
271
|
+
- webhook_limit_exceeded
|
|
272
|
+
- webhook_not_found
|
|
273
|
+
- no_updates_provided
|
|
274
|
+
- output_not_available
|
|
206
275
|
message: { type: string }
|
|
276
|
+
issues:
|
|
277
|
+
description: Validation details for `invalid_body`.
|
|
278
|
+
requiredScope: { type: string }
|
|
279
|
+
required: { type: number }
|
|
280
|
+
available: { type: number }
|
|
207
281
|
|
|
208
282
|
# Bulk Jobs
|
|
209
283
|
BulkCreateRequest:
|
|
@@ -230,6 +304,7 @@ components:
|
|
|
230
304
|
properties:
|
|
231
305
|
totalJobs: { type: integer }
|
|
232
306
|
totalEstimatedNeu: { type: number }
|
|
307
|
+
reserved: { type: number }
|
|
233
308
|
jobs:
|
|
234
309
|
type: array
|
|
235
310
|
items:
|
|
@@ -237,8 +312,17 @@ components:
|
|
|
237
312
|
properties:
|
|
238
313
|
index: { type: integer }
|
|
239
314
|
jobId: { type: string, format: uuid }
|
|
315
|
+
externalId: { type: string, nullable: true }
|
|
316
|
+
metadata:
|
|
317
|
+
type: object
|
|
318
|
+
additionalProperties: true
|
|
319
|
+
nullable: true
|
|
240
320
|
status: { type: string }
|
|
241
321
|
error: { type: string }
|
|
322
|
+
estimate:
|
|
323
|
+
type: object
|
|
324
|
+
properties:
|
|
325
|
+
neu: { type: number }
|
|
242
326
|
BulkStatusResponse:
|
|
243
327
|
type: object
|
|
244
328
|
properties:
|
|
@@ -714,7 +798,9 @@ paths:
|
|
|
714
798
|
post:
|
|
715
799
|
security: [{ BearerAuth: [] }, { ApiKeyAuth: [] }]
|
|
716
800
|
summary: Create a new encode job
|
|
717
|
-
description: Create a job for upload ingest (returns a presigned PUT) or direct URL/S3 ingest.
|
|
801
|
+
description: Create a job for upload ingest (returns a presigned PUT) or direct URL/S3 ingest. Send `Idempotency-Key` when retrying from your backend to avoid duplicate jobs.
|
|
802
|
+
parameters:
|
|
803
|
+
- $ref: "#/components/parameters/IdempotencyKey"
|
|
718
804
|
requestBody:
|
|
719
805
|
required: true
|
|
720
806
|
content:
|
|
@@ -993,6 +1079,9 @@ paths:
|
|
|
993
1079
|
post:
|
|
994
1080
|
security: [{ BearerAuth: [] }, { ApiKeyAuth: [] }]
|
|
995
1081
|
summary: Bulk create jobs
|
|
1082
|
+
description: Send `Idempotency-Key` when retrying from your backend to avoid duplicate bulk batches, duplicate token reservations, or duplicate queue entries.
|
|
1083
|
+
parameters:
|
|
1084
|
+
- $ref: "#/components/parameters/IdempotencyKey"
|
|
996
1085
|
requestBody:
|
|
997
1086
|
required: true
|
|
998
1087
|
content:
|