@verifyapi/sdk 0.1.0

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/dist/errors.js ADDED
@@ -0,0 +1,38 @@
1
+ /**
2
+ * @module @firsthandapi/sdk/errors
3
+ * @description SDK error types for FirstHandAPI API responses.
4
+ */
5
+ /**
6
+ * Error thrown when FirstHandAPI returns a non-2xx response.
7
+ * Contains the parsed error envelope from the response body.
8
+ */
9
+ export class FirstHandApiError extends Error {
10
+ status;
11
+ type;
12
+ requestId;
13
+ details;
14
+ constructor(status, body) {
15
+ super(body.message);
16
+ this.name = 'FirstHandApiError';
17
+ this.status = status;
18
+ this.type = body.type;
19
+ this.requestId = body.request_id;
20
+ this.details = body.details;
21
+ }
22
+ /** True if the error is retryable (429, 500, 502, 503, 504) */
23
+ get retryable() {
24
+ return this.status === 429 || this.status >= 500;
25
+ }
26
+ }
27
+ /**
28
+ * Error thrown when a network failure or timeout occurs.
29
+ */
30
+ export class FirstHandConnectionError extends Error {
31
+ cause;
32
+ constructor(message, cause) {
33
+ super(message);
34
+ this.name = 'FirstHandConnectionError';
35
+ this.cause = cause;
36
+ }
37
+ }
38
+ //# sourceMappingURL=errors.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"errors.js","sourceRoot":"","sources":["../src/errors.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAeH;;;GAGG;AACH,MAAM,OAAO,iBAAkB,SAAQ,KAAK;IACjC,MAAM,CAAS;IACf,IAAI,CAAS;IACb,SAAS,CAAS;IAClB,OAAO,CAA0B;IAE1C,YAAY,MAAc,EAAE,IAAwB;QAClD,KAAK,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;QACpB,IAAI,CAAC,IAAI,GAAG,mBAAmB,CAAC;QAChC,IAAI,CAAC,MAAM,GAAG,MAAM,CAAC;QACrB,IAAI,CAAC,IAAI,GAAG,IAAI,CAAC,IAAI,CAAC;QACtB,IAAI,CAAC,SAAS,GAAG,IAAI,CAAC,UAAU,CAAC;QACjC,IAAI,CAAC,OAAO,GAAG,IAAI,CAAC,OAAO,CAAC;IAC9B,CAAC;IAED,+DAA+D;IAC/D,IAAI,SAAS;QACX,OAAO,IAAI,CAAC,MAAM,KAAK,GAAG,IAAI,IAAI,CAAC,MAAM,IAAI,GAAG,CAAC;IACnD,CAAC;CACF;AAED;;GAEG;AACH,MAAM,OAAO,wBAAyB,SAAQ,KAAK;IACxC,KAAK,CAAS;IAEvB,YAAY,OAAe,EAAE,KAAa;QACxC,KAAK,CAAC,OAAO,CAAC,CAAC;QACf,IAAI,CAAC,IAAI,GAAG,0BAA0B,CAAC;QACvC,IAAI,CAAC,KAAK,GAAG,KAAK,CAAC;IACrB,CAAC;CACF"}
@@ -0,0 +1,24 @@
1
+ /**
2
+ * @module @firsthandapi/sdk
3
+ * @description TypeScript SDK for the FirstHandAPI REST API.
4
+ * Typed client wrapping all v1 endpoints with auto-retry,
5
+ * idempotency key generation, and webhook signature verification.
6
+ *
7
+ * @example
8
+ * ```ts
9
+ * import { FirstHandClient } from '@firsthandapi/sdk';
10
+ *
11
+ * const client = new FirstHandClient({ apiKey: 'fh_live_...' });
12
+ * const job = await client.createJob({
13
+ * job_type: 'product_photo',
14
+ * title: 'Product photo for listing #1234',
15
+ * instructions: 'Take a clean photo of the product on white background',
16
+ * });
17
+ * ```
18
+ */
19
+ export { FirstHandClient, generateIdempotencyKey } from './client.js';
20
+ export type { FirstHandClientOptions, ListResponse, ListOptions, GetJobOptions } from './client.js';
21
+ export { FirstHandApiError, FirstHandConnectionError } from './errors.js';
22
+ export type { FirstHandErrorBody, FirstHandErrorDetail } from './errors.js';
23
+ export { verifyWebhookSignature } from './webhooks.js';
24
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AAEH,OAAO,EAAE,eAAe,EAAE,sBAAsB,EAAE,MAAM,aAAa,CAAC;AACtE,YAAY,EAAE,sBAAsB,EAAE,YAAY,EAAE,WAAW,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AACpG,OAAO,EAAE,iBAAiB,EAAE,wBAAwB,EAAE,MAAM,aAAa,CAAC;AAC1E,YAAY,EAAE,kBAAkB,EAAE,oBAAoB,EAAE,MAAM,aAAa,CAAC;AAC5E,OAAO,EAAE,sBAAsB,EAAE,MAAM,eAAe,CAAC"}
package/dist/index.js ADDED
@@ -0,0 +1,22 @@
1
+ /**
2
+ * @module @firsthandapi/sdk
3
+ * @description TypeScript SDK for the FirstHandAPI REST API.
4
+ * Typed client wrapping all v1 endpoints with auto-retry,
5
+ * idempotency key generation, and webhook signature verification.
6
+ *
7
+ * @example
8
+ * ```ts
9
+ * import { FirstHandClient } from '@firsthandapi/sdk';
10
+ *
11
+ * const client = new FirstHandClient({ apiKey: 'fh_live_...' });
12
+ * const job = await client.createJob({
13
+ * job_type: 'product_photo',
14
+ * title: 'Product photo for listing #1234',
15
+ * instructions: 'Take a clean photo of the product on white background',
16
+ * });
17
+ * ```
18
+ */
19
+ export { FirstHandClient, generateIdempotencyKey } from './client.js';
20
+ export { FirstHandApiError, FirstHandConnectionError } from './errors.js';
21
+ export { verifyWebhookSignature } from './webhooks.js';
22
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AAEH,OAAO,EAAE,eAAe,EAAE,sBAAsB,EAAE,MAAM,aAAa,CAAC;AAEtE,OAAO,EAAE,iBAAiB,EAAE,wBAAwB,EAAE,MAAM,aAAa,CAAC;AAE1E,OAAO,EAAE,sBAAsB,EAAE,MAAM,eAAe,CAAC"}
@@ -0,0 +1,19 @@
1
+ /**
2
+ * @module @firsthandapi/sdk/webhooks
3
+ * @description Webhook signature verification for FirstHandAPI webhook events.
4
+ * Clients use this to verify that webhook payloads were sent by FirstHandAPI.
5
+ *
6
+ * @see webhook-contract.md — Webhook signing specification
7
+ */
8
+ /**
9
+ * Verifies a webhook signature from FirstHandAPI.
10
+ *
11
+ * @param payload - Raw request body (string or Buffer)
12
+ * @param signature - Value of the `X-FirstHandAPI-Signature` header
13
+ * @param secret - Webhook signing secret from endpoint creation
14
+ * @param toleranceSeconds - Maximum age of the event in seconds (default: 300 = 5 minutes)
15
+ * @returns true if the signature is valid
16
+ * @throws Error if signature is invalid, expired, or malformed
17
+ */
18
+ export declare function verifyWebhookSignature(payload: string | Buffer, signature: string, secret: string, toleranceSeconds?: number): boolean;
19
+ //# sourceMappingURL=webhooks.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"webhooks.d.ts","sourceRoot":"","sources":["../src/webhooks.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAIH;;;;;;;;;GASG;AACH,wBAAgB,sBAAsB,CACpC,OAAO,EAAE,MAAM,GAAG,MAAM,EACxB,SAAS,EAAE,MAAM,EACjB,MAAM,EAAE,MAAM,EACd,gBAAgB,SAAM,GACrB,OAAO,CA2CT"}
@@ -0,0 +1,52 @@
1
+ /**
2
+ * @module @firsthandapi/sdk/webhooks
3
+ * @description Webhook signature verification for FirstHandAPI webhook events.
4
+ * Clients use this to verify that webhook payloads were sent by FirstHandAPI.
5
+ *
6
+ * @see webhook-contract.md — Webhook signing specification
7
+ */
8
+ import { createHmac, timingSafeEqual } from 'node:crypto';
9
+ /**
10
+ * Verifies a webhook signature from FirstHandAPI.
11
+ *
12
+ * @param payload - Raw request body (string or Buffer)
13
+ * @param signature - Value of the `X-FirstHandAPI-Signature` header
14
+ * @param secret - Webhook signing secret from endpoint creation
15
+ * @param toleranceSeconds - Maximum age of the event in seconds (default: 300 = 5 minutes)
16
+ * @returns true if the signature is valid
17
+ * @throws Error if signature is invalid, expired, or malformed
18
+ */
19
+ export function verifyWebhookSignature(payload, signature, secret, toleranceSeconds = 300) {
20
+ // Parse signature: "t=<timestamp>,v1=<hash>"
21
+ const parts = signature.split(',');
22
+ const timestampPart = parts.find((p) => p.startsWith('t='));
23
+ const hashPart = parts.find((p) => p.startsWith('v1='));
24
+ if (!timestampPart || !hashPart) {
25
+ throw new Error('Invalid webhook signature format. Expected "t=<timestamp>,v1=<hash>"');
26
+ }
27
+ const timestamp = parseInt(timestampPart.slice(2), 10);
28
+ const expectedHash = hashPart.slice(3);
29
+ if (isNaN(timestamp)) {
30
+ throw new Error('Invalid timestamp in webhook signature');
31
+ }
32
+ // Check tolerance
33
+ const now = Math.floor(Date.now() / 1000);
34
+ if (Math.abs(now - timestamp) > toleranceSeconds) {
35
+ throw new Error(`Webhook timestamp too old (${Math.abs(now - timestamp)}s, tolerance: ${toleranceSeconds}s)`);
36
+ }
37
+ // Compute expected signature
38
+ const body = typeof payload === 'string' ? payload : payload.toString('utf8');
39
+ const signedPayload = `${timestamp}.${body}`;
40
+ const computedHash = createHmac('sha256', secret).update(signedPayload).digest('hex');
41
+ // Timing-safe comparison
42
+ const expected = Buffer.from(expectedHash, 'hex');
43
+ const computed = Buffer.from(computedHash, 'hex');
44
+ if (expected.length !== computed.length) {
45
+ throw new Error('Webhook signature verification failed');
46
+ }
47
+ if (!timingSafeEqual(expected, computed)) {
48
+ throw new Error('Webhook signature verification failed');
49
+ }
50
+ return true;
51
+ }
52
+ //# sourceMappingURL=webhooks.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"webhooks.js","sourceRoot":"","sources":["../src/webhooks.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,EAAE,UAAU,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AAE1D;;;;;;;;;GASG;AACH,MAAM,UAAU,sBAAsB,CACpC,OAAwB,EACxB,SAAiB,EACjB,MAAc,EACd,gBAAgB,GAAG,GAAG;IAEtB,6CAA6C;IAC7C,MAAM,KAAK,GAAG,SAAS,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;IACnC,MAAM,aAAa,GAAG,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,UAAU,CAAC,IAAI,CAAC,CAAC,CAAC;IAC5D,MAAM,QAAQ,GAAG,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,UAAU,CAAC,KAAK,CAAC,CAAC,CAAC;IAExD,IAAI,CAAC,aAAa,IAAI,CAAC,QAAQ,EAAE,CAAC;QAChC,MAAM,IAAI,KAAK,CAAC,sEAAsE,CAAC,CAAC;IAC1F,CAAC;IAED,MAAM,SAAS,GAAG,QAAQ,CAAC,aAAa,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;IACvD,MAAM,YAAY,GAAG,QAAQ,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;IAEvC,IAAI,KAAK,CAAC,SAAS,CAAC,EAAE,CAAC;QACrB,MAAM,IAAI,KAAK,CAAC,wCAAwC,CAAC,CAAC;IAC5D,CAAC;IAED,kBAAkB;IAClB,MAAM,GAAG,GAAG,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,GAAG,EAAE,GAAG,IAAI,CAAC,CAAC;IAC1C,IAAI,IAAI,CAAC,GAAG,CAAC,GAAG,GAAG,SAAS,CAAC,GAAG,gBAAgB,EAAE,CAAC;QACjD,MAAM,IAAI,KAAK,CACb,8BAA8B,IAAI,CAAC,GAAG,CAAC,GAAG,GAAG,SAAS,CAAC,iBAAiB,gBAAgB,IAAI,CAC7F,CAAC;IACJ,CAAC;IAED,6BAA6B;IAC7B,MAAM,IAAI,GAAG,OAAO,OAAO,KAAK,QAAQ,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC;IAC9E,MAAM,aAAa,GAAG,GAAG,SAAS,IAAI,IAAI,EAAE,CAAC;IAC7C,MAAM,YAAY,GAAG,UAAU,CAAC,QAAQ,EAAE,MAAM,CAAC,CAAC,MAAM,CAAC,aAAa,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;IAEtF,yBAAyB;IACzB,MAAM,QAAQ,GAAG,MAAM,CAAC,IAAI,CAAC,YAAY,EAAE,KAAK,CAAC,CAAC;IAClD,MAAM,QAAQ,GAAG,MAAM,CAAC,IAAI,CAAC,YAAY,EAAE,KAAK,CAAC,CAAC;IAElD,IAAI,QAAQ,CAAC,MAAM,KAAK,QAAQ,CAAC,MAAM,EAAE,CAAC;QACxC,MAAM,IAAI,KAAK,CAAC,uCAAuC,CAAC,CAAC;IAC3D,CAAC;IAED,IAAI,CAAC,eAAe,CAAC,QAAQ,EAAE,QAAQ,CAAC,EAAE,CAAC;QACzC,MAAM,IAAI,KAAK,CAAC,uCAAuC,CAAC,CAAC;IAC3D,CAAC;IAED,OAAO,IAAI,CAAC;AACd,CAAC"}
package/package.json ADDED
@@ -0,0 +1,49 @@
1
+ {
2
+ "name": "@verifyapi/sdk",
3
+ "version": "0.1.0",
4
+ "private": false,
5
+ "description": "TypeScript SDK for the FirstHandAPI REST API",
6
+ "type": "module",
7
+ "main": "./dist/index.js",
8
+ "types": "./dist/index.d.ts",
9
+ "exports": {
10
+ ".": {
11
+ "import": "./dist/index.js",
12
+ "types": "./dist/index.d.ts"
13
+ },
14
+ "./client": {
15
+ "import": "./dist/client.js",
16
+ "types": "./dist/client.d.ts"
17
+ }
18
+ },
19
+ "scripts": {
20
+ "build": "tsc",
21
+ "lint": "eslint src/",
22
+ "typecheck": "tsc --noEmit",
23
+ "test:unit": "vitest run",
24
+ "test:integration": "echo 'No integration tests'"
25
+ },
26
+ "dependencies": {
27
+ "@sinclair/typebox": "^0.34.33",
28
+ "@verifyapi/api-contracts": "workspace:*"
29
+ },
30
+ "keywords": [
31
+ "firsthandapi",
32
+ "human-in-the-loop",
33
+ "hitl",
34
+ "crowdsourcing",
35
+ "data-collection",
36
+ "ai",
37
+ "api",
38
+ "media-collection",
39
+ "verification",
40
+ "ugc",
41
+ "ground-truth",
42
+ "training-data",
43
+ "screen-recording"
44
+ ],
45
+ "devDependencies": {
46
+ "@types/node": "^20.0.0",
47
+ "typescript": "^5.7.0"
48
+ }
49
+ }