@myapihq/sdk 1.2.9 → 1.3.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/client.js CHANGED
@@ -64,16 +64,16 @@ async function request(method, url, apiKey, body, extraHeaders) {
64
64
  const err = result?.error;
65
65
  const code = typeof err === 'object' ? (err?.code || 'unknown_error') : (err || 'unknown_error');
66
66
  const detail = typeof err === 'object' ? (err?.message || undefined) : undefined;
67
- const body = typeof err === 'object' ? err : undefined;
68
- throw new MyApiError(code, response.status, detail, body);
67
+ const errBody = typeof err === 'object' ? err : undefined;
68
+ throw new MyApiError(code, response.status, detail, errBody);
69
69
  }
70
70
  const apiResponse = result;
71
71
  if (!apiResponse.success) {
72
72
  const err = apiResponse.error;
73
73
  const code = typeof err === 'object' ? (err?.code || 'unknown_error') : (err || 'unknown_error');
74
74
  const detail = typeof err === 'object' ? (err?.message || undefined) : undefined;
75
- const body = typeof err === 'object' ? err : undefined;
76
- throw new MyApiError(code, response.status, detail, body);
75
+ const errBody = typeof err === 'object' ? err : undefined;
76
+ throw new MyApiError(code, response.status, detail, errBody);
77
77
  }
78
78
  return apiResponse.data;
79
79
  }
package/dist/config.d.ts CHANGED
@@ -5,6 +5,8 @@ export declare const FUNCTION_BASE: string;
5
5
  export declare const PAYMENTS_BASE: string;
6
6
  export declare const CONTAINER_BASE: string;
7
7
  export declare const GIT_BASE: string;
8
+ export declare const QUEUE_BASE: string;
9
+ export declare const TASK_BASE: string;
8
10
  export declare const IMAGE_BASE: string;
9
11
  export declare const WEBHOOK_BASE: string;
10
12
  export declare const WORKFLOW_BASE: string;
package/dist/config.js CHANGED
@@ -8,7 +8,7 @@
8
8
  // local dev — `source scripts/local-env.sh` still points everything at
9
9
  // localhost:8080 regardless of the prod default.
10
10
  Object.defineProperty(exports, "__esModule", { value: true });
11
- exports.CRM_BASE = exports.DATABASE_BASE = exports.LLM_BASE = exports.AUDIENCE_BASE = exports.COMPANY_BASE = exports.PEOPLE_BASE = exports.PIXEL_BASE = exports.URL_BASE = exports.STORAGE_BASE = exports.EMAIL_BASE = exports.WORKFLOW_BASE = exports.WEBHOOK_BASE = exports.IMAGE_BASE = exports.GIT_BASE = exports.CONTAINER_BASE = exports.PAYMENTS_BASE = exports.FUNCTION_BASE = exports.FUNNEL_BASE = exports.DOMAIN_BASE = exports.HQ_BASE = void 0;
11
+ exports.CRM_BASE = exports.DATABASE_BASE = exports.LLM_BASE = exports.AUDIENCE_BASE = exports.COMPANY_BASE = exports.PEOPLE_BASE = exports.PIXEL_BASE = exports.URL_BASE = exports.STORAGE_BASE = exports.EMAIL_BASE = exports.WORKFLOW_BASE = exports.WEBHOOK_BASE = exports.IMAGE_BASE = exports.TASK_BASE = exports.QUEUE_BASE = exports.GIT_BASE = exports.CONTAINER_BASE = exports.PAYMENTS_BASE = exports.FUNCTION_BASE = exports.FUNNEL_BASE = exports.DOMAIN_BASE = exports.HQ_BASE = void 0;
12
12
  const GATEWAY = 'https://api.myapihq.com';
13
13
  exports.HQ_BASE = process.env.MYAPI_HQ_URL ?? process.env.MYAPI_API_BASE ?? GATEWAY;
14
14
  exports.DOMAIN_BASE = process.env.MYAPI_DOMAIN_URL ?? 'https://api.mydomainapi.com';
@@ -19,6 +19,8 @@ exports.FUNCTION_BASE = process.env.MYAPI_FUNCTION_URL ?? GATEWAY;
19
19
  exports.PAYMENTS_BASE = process.env.MYAPI_PAYMENTS_URL ?? GATEWAY;
20
20
  exports.CONTAINER_BASE = process.env.MYAPI_CONTAINER_URL ?? GATEWAY;
21
21
  exports.GIT_BASE = process.env.MYAPI_GIT_URL ?? GATEWAY;
22
+ exports.QUEUE_BASE = process.env.MYAPI_QUEUE_URL ?? GATEWAY;
23
+ exports.TASK_BASE = process.env.MYAPI_TASK_URL ?? GATEWAY;
22
24
  exports.IMAGE_BASE = process.env.MYAPI_IMAGE_URL ?? 'https://api.myimageapi.com';
23
25
  exports.WEBHOOK_BASE = process.env.MYAPI_WEBHOOK_URL ?? 'https://api.mywebhookapi.com';
24
26
  exports.WORKFLOW_BASE = process.env.MYAPI_WORKFLOW_URL ?? 'https://api.myworkflowapi.com';
package/dist/email.d.ts CHANGED
@@ -118,6 +118,11 @@ export declare function activateSending(apiKey: string, address: string): Promis
118
118
  sending_enabled: boolean;
119
119
  emails_quota_remaining: number;
120
120
  }>;
121
+ export declare function setForwarding(apiKey: string, address: string, forwardTo: string): Promise<{
122
+ address: string;
123
+ forward_to: string;
124
+ }>;
125
+ export declare function deleteForwarding(apiKey: string, address: string): Promise<void>;
121
126
  export declare function sendEmail(apiKey: string, payload: {
122
127
  from: string;
123
128
  to: string[];
package/dist/email.js CHANGED
@@ -7,6 +7,8 @@ exports.getVerifyJob = getVerifyJob;
7
7
  exports.createMailbox = createMailbox;
8
8
  exports.listMailboxes = listMailboxes;
9
9
  exports.activateSending = activateSending;
10
+ exports.setForwarding = setForwarding;
11
+ exports.deleteForwarding = deleteForwarding;
10
12
  exports.sendEmail = sendEmail;
11
13
  exports.getEmailStatus = getEmailStatus;
12
14
  exports.getSentEmails = getSentEmails;
@@ -43,6 +45,8 @@ exports.EXPOSES = [
43
45
  // Mailbox / sending activation
44
46
  'POST /email/mailboxes/create',
45
47
  'GET /email/mailboxes',
48
+ 'PUT /email/mailboxes/{address}/forwarding',
49
+ 'DELETE /email/mailboxes/{address}/forwarding',
46
50
  'POST /email/sending/activate',
47
51
  // Send + read
48
52
  'POST /email/send',
@@ -115,6 +119,16 @@ async function listMailboxes(apiKey, options) {
115
119
  async function activateSending(apiKey, address) {
116
120
  return (0, client_1.request)('POST', `${config_1.EMAIL_BASE}/email/sending/activate`, apiKey, { address });
117
121
  }
122
+ // setForwarding redirects a copy of every incoming message to an external
123
+ // address (server-side; the original is kept in the mailbox). `forwardTo`
124
+ // must be a valid address and cannot equal the mailbox itself.
125
+ async function setForwarding(apiKey, address, forwardTo) {
126
+ return (0, client_1.request)('PUT', `${config_1.EMAIL_BASE}/email/mailboxes/${encodeURIComponent(address)}/forwarding`, apiKey, { forward_to: forwardTo });
127
+ }
128
+ // deleteForwarding stops forwarding for a mailbox.
129
+ async function deleteForwarding(apiKey, address) {
130
+ return (0, client_1.request)('DELETE', `${config_1.EMAIL_BASE}/email/mailboxes/${encodeURIComponent(address)}/forwarding`, apiKey);
131
+ }
118
132
  // ── Sending and reading (account-scoped) ─────────────────────────────────────
119
133
  async function sendEmail(apiKey, payload) {
120
134
  return (0, client_1.request)('POST', `${config_1.EMAIL_BASE}/email/send`, apiKey, payload);
package/dist/index.d.ts CHANGED
@@ -20,3 +20,5 @@ export * as fn from './function';
20
20
  export * as payments from './payments';
21
21
  export * as container from './container';
22
22
  export * as git from './git';
23
+ export * as queue from './queue';
24
+ export * as task from './task';
package/dist/index.js CHANGED
@@ -36,7 +36,7 @@ var __importStar = (this && this.__importStar) || (function () {
36
36
  };
37
37
  })();
38
38
  Object.defineProperty(exports, "__esModule", { value: true });
39
- exports.git = exports.container = exports.payments = exports.fn = exports.crm = exports.database = exports.llm = exports.audience = exports.company = exports.people = exports.url = exports.workflow = exports.webhook = exports.storage = exports.pixel = exports.image = exports.funnel = exports.email = exports.domain = exports.hq = void 0;
39
+ exports.task = exports.queue = exports.git = exports.container = exports.payments = exports.fn = exports.crm = exports.database = exports.llm = exports.audience = exports.company = exports.people = exports.url = exports.workflow = exports.webhook = exports.storage = exports.pixel = exports.image = exports.funnel = exports.email = exports.domain = exports.hq = void 0;
40
40
  __exportStar(require("./types"), exports);
41
41
  __exportStar(require("./client"), exports);
42
42
  // Note: config constants (STORAGE_BASE etc.) are NOT re-exported from the
@@ -65,3 +65,5 @@ exports.fn = __importStar(require("./function"));
65
65
  exports.payments = __importStar(require("./payments"));
66
66
  exports.container = __importStar(require("./container"));
67
67
  exports.git = __importStar(require("./git"));
68
+ exports.queue = __importStar(require("./queue"));
69
+ exports.task = __importStar(require("./task"));
@@ -0,0 +1,49 @@
1
+ import type { Exposes } from './exposes';
2
+ export declare const EXPOSES: Exposes;
3
+ export interface Queue {
4
+ id: string;
5
+ org_id: string;
6
+ name: string;
7
+ consumer_url: string;
8
+ max_attempts: number;
9
+ max_concurrency: number;
10
+ created_at: string;
11
+ }
12
+ export type JobStatus = 'pending' | 'blocked' | 'running' | 'succeeded' | 'dead';
13
+ export interface Job {
14
+ id: string;
15
+ queue_id: string;
16
+ org_id: string;
17
+ payload: unknown;
18
+ status: JobStatus;
19
+ attempt: number;
20
+ max_attempts: number;
21
+ not_before: string;
22
+ depends_on: string[];
23
+ dedup_key?: string;
24
+ last_error?: string;
25
+ started_at?: string;
26
+ created_at: string;
27
+ updated_at: string;
28
+ }
29
+ export interface CreateQueueOptions {
30
+ name: string;
31
+ consumerUrl: string;
32
+ maxAttempts?: number;
33
+ maxConcurrency?: number;
34
+ }
35
+ export interface EnqueueOptions {
36
+ payload?: unknown;
37
+ dedupKey?: string;
38
+ delaySeconds?: number;
39
+ dependsOn?: string[];
40
+ }
41
+ export declare function createQueue(apiKey: string, orgId: string, opts: CreateQueueOptions): Promise<Queue>;
42
+ export declare function listQueues(apiKey: string, orgId: string): Promise<Queue[]>;
43
+ export declare function getQueue(apiKey: string, orgId: string, name: string): Promise<Queue>;
44
+ export declare function enqueueJob(apiKey: string, orgId: string, name: string, opts?: EnqueueOptions): Promise<Job>;
45
+ export declare function listJobs(apiKey: string, orgId: string, name: string, opts?: {
46
+ status?: string;
47
+ limit?: number;
48
+ }): Promise<Job[]>;
49
+ export declare function getJob(apiKey: string, orgId: string, jobId: string): Promise<Job>;
package/dist/queue.js ADDED
@@ -0,0 +1,71 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.EXPOSES = void 0;
4
+ exports.createQueue = createQueue;
5
+ exports.listQueues = listQueues;
6
+ exports.getQueue = getQueue;
7
+ exports.enqueueJob = enqueueJob;
8
+ exports.listJobs = listJobs;
9
+ exports.getJob = getJob;
10
+ const client_1 = require("./client");
11
+ const config_1 = require("./config");
12
+ // Backend: my-queue-api per myapi-hq/internal/routes/queue/. A durable
13
+ // HTTP-consumer job queue — each named queue POSTs its jobs to a configured
14
+ // consumer_url, with retry/backoff to max_attempts, a concurrency cap, and a
15
+ // dependency DAG (a job with unsucceeded depends_on starts blocked).
16
+ exports.EXPOSES = [
17
+ 'POST /queue/orgs/{org_id}/queues',
18
+ 'GET /queue/orgs/{org_id}/queues',
19
+ 'GET /queue/orgs/{org_id}/queues/{name}',
20
+ 'POST /queue/orgs/{org_id}/queues/{name}/jobs',
21
+ 'GET /queue/orgs/{org_id}/queues/{name}/jobs',
22
+ 'GET /queue/orgs/{org_id}/jobs/{id}',
23
+ ];
24
+ function queuesBase(orgId) {
25
+ return `${config_1.QUEUE_BASE}/queue/orgs/${encodeURIComponent(orgId)}/queues`;
26
+ }
27
+ // createQueue registers a named queue with its retry + concurrency policy
28
+ // and the HTTP consumer that runs its jobs.
29
+ async function createQueue(apiKey, orgId, opts) {
30
+ const body = { name: opts.name, consumer_url: opts.consumerUrl };
31
+ if (opts.maxAttempts !== undefined)
32
+ body.max_attempts = opts.maxAttempts;
33
+ if (opts.maxConcurrency !== undefined)
34
+ body.max_concurrency = opts.maxConcurrency;
35
+ return (0, client_1.request)('POST', queuesBase(orgId), apiKey, body);
36
+ }
37
+ async function listQueues(apiKey, orgId) {
38
+ const res = await (0, client_1.request)('GET', queuesBase(orgId), apiKey);
39
+ return res?.queues ?? [];
40
+ }
41
+ async function getQueue(apiKey, orgId, name) {
42
+ return (0, client_1.request)('GET', `${queuesBase(orgId)}/${encodeURIComponent(name)}`, apiKey);
43
+ }
44
+ // enqueueJob is idempotent on (queue, dedup_key). A job with unsucceeded
45
+ // depends_on starts blocked.
46
+ async function enqueueJob(apiKey, orgId, name, opts = {}) {
47
+ const body = {};
48
+ if (opts.payload !== undefined)
49
+ body.payload = opts.payload;
50
+ if (opts.dedupKey !== undefined)
51
+ body.dedup_key = opts.dedupKey;
52
+ if (opts.delaySeconds !== undefined)
53
+ body.delay_seconds = opts.delaySeconds;
54
+ if (opts.dependsOn !== undefined)
55
+ body.depends_on = opts.dependsOn;
56
+ return (0, client_1.request)('POST', `${queuesBase(orgId)}/${encodeURIComponent(name)}/jobs`, apiKey, body);
57
+ }
58
+ // listJobs lists a queue's jobs, newest first. `limit` is 1-200.
59
+ async function listJobs(apiKey, orgId, name, opts = {}) {
60
+ const q = new URLSearchParams();
61
+ if (opts.status)
62
+ q.set('status', opts.status);
63
+ if (opts.limit !== undefined)
64
+ q.set('limit', String(opts.limit));
65
+ const qs = q.toString();
66
+ const res = await (0, client_1.request)('GET', `${queuesBase(orgId)}/${encodeURIComponent(name)}/jobs${qs ? `?${qs}` : ''}`, apiKey);
67
+ return res?.jobs ?? [];
68
+ }
69
+ async function getJob(apiKey, orgId, jobId) {
70
+ return (0, client_1.request)('GET', `${config_1.QUEUE_BASE}/queue/orgs/${encodeURIComponent(orgId)}/jobs/${encodeURIComponent(jobId)}`, apiKey);
71
+ }
@@ -1,5 +1,5 @@
1
1
  export type ServiceStatus = 'ga' | 'preview' | 'planned';
2
- export type ServiceCategory = 'send' | 'capture' | 'data' | 'store' | 'compute' | 'identity' | 'infra';
2
+ export type ServiceCategory = 'send' | 'capture' | 'data' | 'store' | 'compute' | 'orchestrate' | 'identity' | 'infra';
3
3
  export interface ServiceMeta {
4
4
  /** SDK namespace (also OpenAPI tag and CLI top-level command). */
5
5
  module: string;
package/dist/services.js CHANGED
@@ -171,15 +171,6 @@ exports.SERVICES = [
171
171
  keywords: k('kv-store', 'database', 'state'),
172
172
  },
173
173
  // ── compute ─────────────────────────────────────────────────────────
174
- {
175
- module: 'workflow',
176
- skill: 'my-workflow-api',
177
- domain: 'myworkflowapi.com',
178
- description: 'React to inbound webhooks with step chains: send email, post to Slack, call HTTP URLs.',
179
- category: 'compute',
180
- status: 'ga',
181
- keywords: k('workflow', 'automation', 'orchestration'),
182
- },
183
174
  {
184
175
  module: 'llm',
185
176
  skill: 'my-llm-api',
@@ -248,4 +239,41 @@ exports.SERVICES = [
248
239
  status: 'preview',
249
240
  keywords: k('git', 'repository', 'version-control', 'commit', 'scm'),
250
241
  },
242
+ // ── orchestrate ─────────────────────────────────────────────────────
243
+ // Control flow, not actions. workflow = event→action, queue = durable
244
+ // async machine work, task = work that needs an agent/human decision.
245
+ // See docs/orchestration-decision-guide.md.
246
+ {
247
+ module: 'workflow',
248
+ skill: 'my-workflow-api',
249
+ domain: 'myworkflowapi.com',
250
+ description: 'React to inbound webhooks with step chains: send email, post to Slack, call HTTP URLs.',
251
+ category: 'orchestrate',
252
+ status: 'ga',
253
+ keywords: k('workflow', 'automation', 'orchestration'),
254
+ },
255
+ {
256
+ // CLI top-level command + SDK namespace are both `queue`; skill
257
+ // directory is `my-queue-api`. A durable HTTP-consumer job queue —
258
+ // retry/backoff, concurrency caps, dependency DAG.
259
+ module: 'queue',
260
+ skill: 'my-queue-api',
261
+ domain: 'myqueueapi.com',
262
+ description: 'Durable job queue — enqueue work and have it retried against your HTTP consumer, with concurrency caps and a dependency DAG.',
263
+ category: 'orchestrate',
264
+ status: 'preview',
265
+ keywords: k('queue', 'job-queue', 'background-jobs', 'retry', 'async'),
266
+ },
267
+ {
268
+ // CLI top-level command + SDK namespace are both `task`; skill
269
+ // directory is `my-task-api`. The agent-task queue — the agent-loop
270
+ // hot path: file, claim under a lease, resolve.
271
+ module: 'task',
272
+ skill: 'my-task-api',
273
+ domain: 'mytaskapi.com',
274
+ description: 'Agent-task queue — file units of work, rank them, claim under a lease, then resolve, fail, or cancel. The agent-loop hot path.',
275
+ category: 'orchestrate',
276
+ status: 'preview',
277
+ keywords: k('task', 'task-queue', 'agent-loop', 'work-queue', 'lease'),
278
+ },
251
279
  ];
package/dist/task.d.ts ADDED
@@ -0,0 +1,78 @@
1
+ import type { Exposes } from './exposes';
2
+ export declare const EXPOSES: Exposes;
3
+ export type TaskStatus = 'open' | 'claimed' | 'blocked' | 'resolved' | 'failed' | 'cancelled';
4
+ export type TaskImportance = 'low' | 'normal' | 'high' | 'critical';
5
+ export interface ResolveOn {
6
+ event_type: string;
7
+ field?: string;
8
+ value?: string;
9
+ }
10
+ export interface TaskRef {
11
+ id: string;
12
+ description: string;
13
+ score: number;
14
+ }
15
+ export interface Task {
16
+ id: string;
17
+ org_id: string;
18
+ description: string;
19
+ status: TaskStatus;
20
+ importance: TaskImportance;
21
+ score: number;
22
+ tags: string[];
23
+ source: string;
24
+ depends_on: string[];
25
+ payload_url: string;
26
+ assignee?: string;
27
+ resolve_on?: ResolveOn;
28
+ dedup_key?: string;
29
+ claimed_by?: string;
30
+ lease_expires_at?: string;
31
+ due_at?: string;
32
+ fail_reason?: string;
33
+ resolved_at?: string;
34
+ created_at: string;
35
+ updated_at: string;
36
+ }
37
+ export interface TaskListMeta {
38
+ shown: number;
39
+ total_open: number;
40
+ }
41
+ export interface TaskListResult {
42
+ tasks: TaskRef[];
43
+ meta: TaskListMeta;
44
+ }
45
+ export interface CreateTaskOptions {
46
+ description: string;
47
+ body?: string;
48
+ importance?: TaskImportance;
49
+ dueAt?: string;
50
+ assignee?: string;
51
+ tags?: string[];
52
+ dependsOn?: string[];
53
+ dedupKey?: string;
54
+ resolveOn?: ResolveOn;
55
+ source?: string;
56
+ }
57
+ export interface ListTaskOptions {
58
+ status?: string;
59
+ tag?: string;
60
+ importance?: string;
61
+ assignee?: string;
62
+ source?: string;
63
+ limit?: number;
64
+ }
65
+ export declare function createTask(apiKey: string, orgId: string, opts: CreateTaskOptions): Promise<Task>;
66
+ export declare function listTasks(apiKey: string, orgId: string, opts?: ListTaskOptions): Promise<TaskListResult>;
67
+ export declare function getTask(apiKey: string, orgId: string, id: string): Promise<Task>;
68
+ export declare function getTaskBody(apiKey: string, orgId: string, id: string): Promise<string>;
69
+ export declare function claimTask(apiKey: string, orgId: string, id: string, opts?: {
70
+ leaseSeconds?: number;
71
+ worker?: string;
72
+ }): Promise<Task>;
73
+ export declare function extendTask(apiKey: string, orgId: string, id: string, opts?: {
74
+ leaseSeconds?: number;
75
+ }): Promise<Task>;
76
+ export declare function resolveTask(apiKey: string, orgId: string, id: string): Promise<Task>;
77
+ export declare function failTask(apiKey: string, orgId: string, id: string, reason: string): Promise<Task>;
78
+ export declare function cancelTask(apiKey: string, orgId: string, id: string): Promise<void>;
package/dist/task.js ADDED
@@ -0,0 +1,131 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.EXPOSES = void 0;
4
+ exports.createTask = createTask;
5
+ exports.listTasks = listTasks;
6
+ exports.getTask = getTask;
7
+ exports.getTaskBody = getTaskBody;
8
+ exports.claimTask = claimTask;
9
+ exports.extendTask = extendTask;
10
+ exports.resolveTask = resolveTask;
11
+ exports.failTask = failTask;
12
+ exports.cancelTask = cancelTask;
13
+ const client_1 = require("./client");
14
+ const config_1 = require("./config");
15
+ // Backend: my-task-api per myapi-hq/internal/routes/task/. An agent-task
16
+ // queue — the agent-loop hot path. Tasks are created, ranked by score,
17
+ // claimed under a lease, then resolved/failed/cancelled. A task with
18
+ // unresolved depends_on starts blocked; lease expiry auto-reverts a claimed
19
+ // task to open.
20
+ exports.EXPOSES = [
21
+ 'POST /task/orgs/{org_id}/tasks',
22
+ 'GET /task/orgs/{org_id}/tasks',
23
+ 'GET /task/orgs/{org_id}/tasks/{id}',
24
+ 'DELETE /task/orgs/{org_id}/tasks/{id}',
25
+ 'GET /task/orgs/{org_id}/tasks/{id}/body',
26
+ 'POST /task/orgs/{org_id}/tasks/{id}/claim',
27
+ 'POST /task/orgs/{org_id}/tasks/{id}/extend',
28
+ 'POST /task/orgs/{org_id}/tasks/{id}/fail',
29
+ 'POST /task/orgs/{org_id}/tasks/{id}/resolve',
30
+ ];
31
+ function tasksBase(orgId) {
32
+ return `${config_1.TASK_BASE}/task/orgs/${encodeURIComponent(orgId)}/tasks`;
33
+ }
34
+ function taskBase(orgId, id) {
35
+ return `${tasksBase(orgId)}/${encodeURIComponent(id)}`;
36
+ }
37
+ // createTask is idempotent on (org_id, dedup_key). A task with unresolved
38
+ // depends_on starts blocked; one with an assignee emails them a magic link.
39
+ async function createTask(apiKey, orgId, opts) {
40
+ const body = { description: opts.description };
41
+ if (opts.body !== undefined)
42
+ body.body = opts.body;
43
+ if (opts.importance !== undefined)
44
+ body.importance = opts.importance;
45
+ if (opts.dueAt !== undefined)
46
+ body.due_at = opts.dueAt;
47
+ if (opts.assignee !== undefined)
48
+ body.assignee = opts.assignee;
49
+ if (opts.tags !== undefined)
50
+ body.tags = opts.tags;
51
+ if (opts.dependsOn !== undefined)
52
+ body.depends_on = opts.dependsOn;
53
+ if (opts.dedupKey !== undefined)
54
+ body.dedup_key = opts.dedupKey;
55
+ if (opts.resolveOn !== undefined)
56
+ body.resolve_on = opts.resolveOn;
57
+ if (opts.source !== undefined)
58
+ body.source = opts.source;
59
+ return (0, client_1.request)('POST', tasksBase(orgId), apiKey, body);
60
+ }
61
+ // listTasks returns the ranked open queue (projected to TaskRef) plus the
62
+ // backlog counts. Default: top 20 open tasks by score. The backend nests
63
+ // {shown, total_open} inside the response `data.meta` (not the envelope meta).
64
+ async function listTasks(apiKey, orgId, opts = {}) {
65
+ const q = new URLSearchParams();
66
+ if (opts.status)
67
+ q.set('status', opts.status);
68
+ if (opts.tag)
69
+ q.set('tag', opts.tag);
70
+ if (opts.importance)
71
+ q.set('importance', opts.importance);
72
+ if (opts.assignee)
73
+ q.set('assignee', opts.assignee);
74
+ if (opts.source)
75
+ q.set('source', opts.source);
76
+ if (opts.limit !== undefined)
77
+ q.set('limit', String(opts.limit));
78
+ const qs = q.toString();
79
+ const res = await (0, client_1.request)('GET', `${tasksBase(orgId)}${qs ? `?${qs}` : ''}`, apiKey);
80
+ const tasks = res?.tasks ?? [];
81
+ const m = res?.meta ?? {};
82
+ return {
83
+ tasks,
84
+ meta: {
85
+ shown: typeof m.shown === 'number' ? m.shown : tasks.length,
86
+ total_open: typeof m.total_open === 'number' ? m.total_open : tasks.length,
87
+ },
88
+ };
89
+ }
90
+ // getTask returns the full task object. It deliberately does NOT fetch the
91
+ // Markdown body tier — call getTaskBody for that, once, on commit.
92
+ async function getTask(apiKey, orgId, id) {
93
+ return (0, client_1.request)('GET', taskBase(orgId, id), apiKey);
94
+ }
95
+ // getTaskBody fetches the body tier — the full Markdown context. Separate
96
+ // call by design: it keeps the list/get path token-cheap. The endpoint
97
+ // returns { task_id, body }.
98
+ async function getTaskBody(apiKey, orgId, id) {
99
+ const res = await (0, client_1.request)('GET', `${taskBase(orgId, id)}/body`, apiKey);
100
+ return res?.body ?? '';
101
+ }
102
+ // claimTask takes an atomic lease (default 10 min) and returns the full
103
+ // updated task — lease state is on `lease_expires_at` / `claimed_by`.
104
+ async function claimTask(apiKey, orgId, id, opts = {}) {
105
+ const body = {};
106
+ if (opts.leaseSeconds !== undefined)
107
+ body.lease_seconds = opts.leaseSeconds;
108
+ if (opts.worker !== undefined)
109
+ body.worker = opts.worker;
110
+ return (0, client_1.request)('POST', `${taskBase(orgId, id)}/claim`, apiKey, body);
111
+ }
112
+ // extendTask is a heartbeat for long work — extends a live claim.
113
+ async function extendTask(apiKey, orgId, id, opts = {}) {
114
+ const body = {};
115
+ if (opts.leaseSeconds !== undefined)
116
+ body.lease_seconds = opts.leaseSeconds;
117
+ return (0, client_1.request)('POST', `${taskBase(orgId, id)}/extend`, apiKey, body);
118
+ }
119
+ // resolveTask resolves a task (terminal) — unblocks any dependents.
120
+ async function resolveTask(apiKey, orgId, id) {
121
+ return (0, client_1.request)('POST', `${taskBase(orgId, id)}/resolve`, apiKey, {});
122
+ }
123
+ // failTask fails a task (terminal, with a reason). Does not auto-retry;
124
+ // dependents that can never proceed are auto-failed.
125
+ async function failTask(apiKey, orgId, id, reason) {
126
+ return (0, client_1.request)('POST', `${taskBase(orgId, id)}/fail`, apiKey, { reason });
127
+ }
128
+ // cancelTask cancels a task (terminal, distinct from fail).
129
+ async function cancelTask(apiKey, orgId, id) {
130
+ return (0, client_1.request)('DELETE', taskBase(orgId, id), apiKey);
131
+ }
@@ -28,6 +28,12 @@ export type WorkflowStep = {
28
28
  method?: 'GET' | 'POST' | 'PATCH' | 'PUT' | 'DELETE';
29
29
  body?: string;
30
30
  headers?: Record<string, string>;
31
+ } | {
32
+ type: 'enqueue_job' | 'enqueue';
33
+ queue: string;
34
+ payload?: string;
35
+ dedup_key?: string;
36
+ delay_seconds?: number;
31
37
  };
32
38
  export interface Workflow {
33
39
  id: string;
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@myapihq/sdk",
3
3
  "license": "Apache-2.0",
4
- "version": "1.2.9",
4
+ "version": "1.3.0",
5
5
  "description": "TypeScript SDK for the MyAPI ecosystem",
6
6
  "main": "dist/index.js",
7
7
  "types": "dist/index.d.ts",
package/src/client.ts CHANGED
@@ -72,8 +72,8 @@ export async function request<T>(
72
72
  const err = result?.error;
73
73
  const code = typeof err === 'object' ? (err?.code || 'unknown_error') : (err || 'unknown_error');
74
74
  const detail = typeof err === 'object' ? (err?.message || undefined) : undefined;
75
- const body = typeof err === 'object' ? err : undefined;
76
- throw new MyApiError(code, response.status, detail, body);
75
+ const errBody = typeof err === 'object' ? err : undefined;
76
+ throw new MyApiError(code, response.status, detail, errBody);
77
77
  }
78
78
 
79
79
  const apiResponse = result as ApiResponse<T>;
@@ -81,8 +81,8 @@ export async function request<T>(
81
81
  const err = apiResponse.error as any;
82
82
  const code = typeof err === 'object' ? (err?.code || 'unknown_error') : (err || 'unknown_error');
83
83
  const detail = typeof err === 'object' ? (err?.message || undefined) : undefined;
84
- const body = typeof err === 'object' ? err : undefined;
85
- throw new MyApiError(code, response.status, detail, body);
84
+ const errBody = typeof err === 'object' ? err : undefined;
85
+ throw new MyApiError(code, response.status, detail, errBody);
86
86
  }
87
87
 
88
88
  return apiResponse.data as T;
package/src/config.ts CHANGED
@@ -18,6 +18,8 @@ export const FUNCTION_BASE= process.env.MYAPI_FUNCTION_URL?? GATEWAY;
18
18
  export const PAYMENTS_BASE= process.env.MYAPI_PAYMENTS_URL?? GATEWAY;
19
19
  export const CONTAINER_BASE=process.env.MYAPI_CONTAINER_URL?? GATEWAY;
20
20
  export const GIT_BASE = process.env.MYAPI_GIT_URL ?? GATEWAY;
21
+ export const QUEUE_BASE = process.env.MYAPI_QUEUE_URL ?? GATEWAY;
22
+ export const TASK_BASE = process.env.MYAPI_TASK_URL ?? GATEWAY;
21
23
  export const IMAGE_BASE = process.env.MYAPI_IMAGE_URL ?? 'https://api.myimageapi.com';
22
24
  export const WEBHOOK_BASE = process.env.MYAPI_WEBHOOK_URL ?? 'https://api.mywebhookapi.com';
23
25
  export const WORKFLOW_BASE= process.env.MYAPI_WORKFLOW_URL?? 'https://api.myworkflowapi.com';
package/src/email.ts CHANGED
@@ -6,6 +6,8 @@ export const EXPOSES: Exposes = [
6
6
  // Mailbox / sending activation
7
7
  'POST /email/mailboxes/create',
8
8
  'GET /email/mailboxes',
9
+ 'PUT /email/mailboxes/{address}/forwarding',
10
+ 'DELETE /email/mailboxes/{address}/forwarding',
9
11
  'POST /email/sending/activate',
10
12
  // Send + read
11
13
  'POST /email/send',
@@ -151,6 +153,18 @@ export async function activateSending(apiKey: string, address: string): Promise<
151
153
  return request('POST', `${BASE_URL}/email/sending/activate`, apiKey, { address });
152
154
  }
153
155
 
156
+ // setForwarding redirects a copy of every incoming message to an external
157
+ // address (server-side; the original is kept in the mailbox). `forwardTo`
158
+ // must be a valid address and cannot equal the mailbox itself.
159
+ export async function setForwarding(apiKey: string, address: string, forwardTo: string): Promise<{ address: string; forward_to: string }> {
160
+ return request('PUT', `${BASE_URL}/email/mailboxes/${encodeURIComponent(address)}/forwarding`, apiKey, { forward_to: forwardTo });
161
+ }
162
+
163
+ // deleteForwarding stops forwarding for a mailbox.
164
+ export async function deleteForwarding(apiKey: string, address: string): Promise<void> {
165
+ return request('DELETE', `${BASE_URL}/email/mailboxes/${encodeURIComponent(address)}/forwarding`, apiKey);
166
+ }
167
+
154
168
  // ── Sending and reading (account-scoped) ─────────────────────────────────────
155
169
 
156
170
  export async function sendEmail(apiKey: string, payload: { from: string; to: string[]; subject: string; html?: string; text?: string; template_id?: string; template_vars?: Record<string, string> }): Promise<{ message_id: string }> {
package/src/index.ts CHANGED
@@ -26,3 +26,5 @@ export * as fn from './function';
26
26
  export * as payments from './payments';
27
27
  export * as container from './container';
28
28
  export * as git from './git';
29
+ export * as queue from './queue';
30
+ export * as task from './task';
package/src/queue.ts ADDED
@@ -0,0 +1,110 @@
1
+ import { request } from './client';
2
+ import { QUEUE_BASE as BASE_URL } from './config';
3
+ import type { Exposes } from './exposes';
4
+
5
+ // Backend: my-queue-api per myapi-hq/internal/routes/queue/. A durable
6
+ // HTTP-consumer job queue — each named queue POSTs its jobs to a configured
7
+ // consumer_url, with retry/backoff to max_attempts, a concurrency cap, and a
8
+ // dependency DAG (a job with unsucceeded depends_on starts blocked).
9
+ export const EXPOSES: Exposes = [
10
+ 'POST /queue/orgs/{org_id}/queues',
11
+ 'GET /queue/orgs/{org_id}/queues',
12
+ 'GET /queue/orgs/{org_id}/queues/{name}',
13
+ 'POST /queue/orgs/{org_id}/queues/{name}/jobs',
14
+ 'GET /queue/orgs/{org_id}/queues/{name}/jobs',
15
+ 'GET /queue/orgs/{org_id}/jobs/{id}',
16
+ ];
17
+
18
+ export interface Queue {
19
+ id: string;
20
+ org_id: string;
21
+ name: string;
22
+ consumer_url: string;
23
+ max_attempts: number;
24
+ max_concurrency: number;
25
+ created_at: string;
26
+ }
27
+
28
+ // A job that exhausts max_attempts goes straight to `dead` — there is no
29
+ // separate `failed` state.
30
+ export type JobStatus = 'pending' | 'blocked' | 'running' | 'succeeded' | 'dead';
31
+
32
+ export interface Job {
33
+ id: string;
34
+ queue_id: string;
35
+ org_id: string;
36
+ payload: unknown;
37
+ status: JobStatus;
38
+ attempt: number;
39
+ max_attempts: number;
40
+ not_before: string;
41
+ depends_on: string[];
42
+ dedup_key?: string;
43
+ last_error?: string;
44
+ started_at?: string;
45
+ created_at: string;
46
+ updated_at: string;
47
+ }
48
+
49
+ export interface CreateQueueOptions {
50
+ name: string;
51
+ consumerUrl: string;
52
+ maxAttempts?: number;
53
+ maxConcurrency?: number;
54
+ }
55
+
56
+ export interface EnqueueOptions {
57
+ payload?: unknown;
58
+ dedupKey?: string;
59
+ delaySeconds?: number;
60
+ // Job ids this job waits on. Immutable — the DAG is declared at enqueue.
61
+ // Same shape as task.create's depends_on.
62
+ dependsOn?: string[];
63
+ }
64
+
65
+ function queuesBase(orgId: string): string {
66
+ return `${BASE_URL}/queue/orgs/${encodeURIComponent(orgId)}/queues`;
67
+ }
68
+
69
+ // createQueue registers a named queue with its retry + concurrency policy
70
+ // and the HTTP consumer that runs its jobs.
71
+ export async function createQueue(apiKey: string, orgId: string, opts: CreateQueueOptions): Promise<Queue> {
72
+ const body: Record<string, unknown> = { name: opts.name, consumer_url: opts.consumerUrl };
73
+ if (opts.maxAttempts !== undefined) body.max_attempts = opts.maxAttempts;
74
+ if (opts.maxConcurrency !== undefined) body.max_concurrency = opts.maxConcurrency;
75
+ return request('POST', queuesBase(orgId), apiKey, body);
76
+ }
77
+
78
+ export async function listQueues(apiKey: string, orgId: string): Promise<Queue[]> {
79
+ const res = await request<{ queues?: Queue[] }>('GET', queuesBase(orgId), apiKey);
80
+ return res?.queues ?? [];
81
+ }
82
+
83
+ export async function getQueue(apiKey: string, orgId: string, name: string): Promise<Queue> {
84
+ return request('GET', `${queuesBase(orgId)}/${encodeURIComponent(name)}`, apiKey);
85
+ }
86
+
87
+ // enqueueJob is idempotent on (queue, dedup_key). A job with unsucceeded
88
+ // depends_on starts blocked.
89
+ export async function enqueueJob(apiKey: string, orgId: string, name: string, opts: EnqueueOptions = {}): Promise<Job> {
90
+ const body: Record<string, unknown> = {};
91
+ if (opts.payload !== undefined) body.payload = opts.payload;
92
+ if (opts.dedupKey !== undefined) body.dedup_key = opts.dedupKey;
93
+ if (opts.delaySeconds !== undefined) body.delay_seconds = opts.delaySeconds;
94
+ if (opts.dependsOn !== undefined) body.depends_on = opts.dependsOn;
95
+ return request('POST', `${queuesBase(orgId)}/${encodeURIComponent(name)}/jobs`, apiKey, body);
96
+ }
97
+
98
+ // listJobs lists a queue's jobs, newest first. `limit` is 1-200.
99
+ export async function listJobs(apiKey: string, orgId: string, name: string, opts: { status?: string; limit?: number } = {}): Promise<Job[]> {
100
+ const q = new URLSearchParams();
101
+ if (opts.status) q.set('status', opts.status);
102
+ if (opts.limit !== undefined) q.set('limit', String(opts.limit));
103
+ const qs = q.toString();
104
+ const res = await request<{ jobs?: Job[] }>('GET', `${queuesBase(orgId)}/${encodeURIComponent(name)}/jobs${qs ? `?${qs}` : ''}`, apiKey);
105
+ return res?.jobs ?? [];
106
+ }
107
+
108
+ export async function getJob(apiKey: string, orgId: string, jobId: string): Promise<Job> {
109
+ return request('GET', `${BASE_URL}/queue/orgs/${encodeURIComponent(orgId)}/jobs/${encodeURIComponent(jobId)}`, apiKey);
110
+ }
package/src/services.ts CHANGED
@@ -20,7 +20,8 @@ export type ServiceCategory =
20
20
  | 'capture' // inbound: webhook, pixel
21
21
  | 'data' // read/filter the data layer: people, company, audience, crm
22
22
  | 'store' // persist bytes/JSON: storage, database
23
- | 'compute' // run code or inference: llm, workflow
23
+ | 'compute' // run code or inference: llm, fn
24
+ | 'orchestrate' // control flow: workflow, queue, task
24
25
  | 'identity' // auth/billing/account: hq
25
26
  | 'infra'; // platform glue: domain
26
27
 
@@ -201,15 +202,6 @@ export const SERVICES: readonly ServiceMeta[] = [
201
202
  },
202
203
 
203
204
  // ── compute ─────────────────────────────────────────────────────────
204
- {
205
- module: 'workflow',
206
- skill: 'my-workflow-api',
207
- domain: 'myworkflowapi.com',
208
- description: 'React to inbound webhooks with step chains: send email, post to Slack, call HTTP URLs.',
209
- category: 'compute',
210
- status: 'ga',
211
- keywords: k('workflow', 'automation', 'orchestration'),
212
- },
213
205
  {
214
206
  module: 'llm',
215
207
  skill: 'my-llm-api',
@@ -278,4 +270,42 @@ export const SERVICES: readonly ServiceMeta[] = [
278
270
  status: 'preview',
279
271
  keywords: k('git', 'repository', 'version-control', 'commit', 'scm'),
280
272
  },
273
+
274
+ // ── orchestrate ─────────────────────────────────────────────────────
275
+ // Control flow, not actions. workflow = event→action, queue = durable
276
+ // async machine work, task = work that needs an agent/human decision.
277
+ // See docs/orchestration-decision-guide.md.
278
+ {
279
+ module: 'workflow',
280
+ skill: 'my-workflow-api',
281
+ domain: 'myworkflowapi.com',
282
+ description: 'React to inbound webhooks with step chains: send email, post to Slack, call HTTP URLs.',
283
+ category: 'orchestrate',
284
+ status: 'ga',
285
+ keywords: k('workflow', 'automation', 'orchestration'),
286
+ },
287
+ {
288
+ // CLI top-level command + SDK namespace are both `queue`; skill
289
+ // directory is `my-queue-api`. A durable HTTP-consumer job queue —
290
+ // retry/backoff, concurrency caps, dependency DAG.
291
+ module: 'queue',
292
+ skill: 'my-queue-api',
293
+ domain: 'myqueueapi.com',
294
+ description: 'Durable job queue — enqueue work and have it retried against your HTTP consumer, with concurrency caps and a dependency DAG.',
295
+ category: 'orchestrate',
296
+ status: 'preview',
297
+ keywords: k('queue', 'job-queue', 'background-jobs', 'retry', 'async'),
298
+ },
299
+ {
300
+ // CLI top-level command + SDK namespace are both `task`; skill
301
+ // directory is `my-task-api`. The agent-task queue — the agent-loop
302
+ // hot path: file, claim under a lease, resolve.
303
+ module: 'task',
304
+ skill: 'my-task-api',
305
+ domain: 'mytaskapi.com',
306
+ description: 'Agent-task queue — file units of work, rank them, claim under a lease, then resolve, fail, or cancel. The agent-loop hot path.',
307
+ category: 'orchestrate',
308
+ status: 'preview',
309
+ keywords: k('task', 'task-queue', 'agent-loop', 'work-queue', 'lease'),
310
+ },
281
311
  ] as const;
package/src/task.ts ADDED
@@ -0,0 +1,198 @@
1
+ import { request } from './client';
2
+ import { TASK_BASE as BASE_URL } from './config';
3
+ import type { Exposes } from './exposes';
4
+
5
+ // Backend: my-task-api per myapi-hq/internal/routes/task/. An agent-task
6
+ // queue — the agent-loop hot path. Tasks are created, ranked by score,
7
+ // claimed under a lease, then resolved/failed/cancelled. A task with
8
+ // unresolved depends_on starts blocked; lease expiry auto-reverts a claimed
9
+ // task to open.
10
+ export const EXPOSES: Exposes = [
11
+ 'POST /task/orgs/{org_id}/tasks',
12
+ 'GET /task/orgs/{org_id}/tasks',
13
+ 'GET /task/orgs/{org_id}/tasks/{id}',
14
+ 'DELETE /task/orgs/{org_id}/tasks/{id}',
15
+ 'GET /task/orgs/{org_id}/tasks/{id}/body',
16
+ 'POST /task/orgs/{org_id}/tasks/{id}/claim',
17
+ 'POST /task/orgs/{org_id}/tasks/{id}/extend',
18
+ 'POST /task/orgs/{org_id}/tasks/{id}/fail',
19
+ 'POST /task/orgs/{org_id}/tasks/{id}/resolve',
20
+ ];
21
+
22
+ export type TaskStatus = 'open' | 'claimed' | 'blocked' | 'resolved' | 'failed' | 'cancelled';
23
+
24
+ export type TaskImportance = 'low' | 'normal' | 'high' | 'critical';
25
+
26
+ // Event matcher: a matching platform_events delivery auto-resolves the task.
27
+ export interface ResolveOn {
28
+ event_type: string;
29
+ field?: string;
30
+ value?: string;
31
+ }
32
+
33
+ // The projected list row — list deliberately returns only this triage-sized
34
+ // shape, not the full task. Read the body tier separately via getTaskBody.
35
+ export interface TaskRef {
36
+ id: string;
37
+ description: string;
38
+ score: number;
39
+ }
40
+
41
+ // Full task object. `claim` and `extend` return this — lease state is read
42
+ // off `lease_expires_at` / `claimed_by`, not a separate lease object.
43
+ export interface Task {
44
+ id: string;
45
+ org_id: string;
46
+ description: string;
47
+ status: TaskStatus;
48
+ importance: TaskImportance;
49
+ score: number;
50
+ tags: string[];
51
+ source: string;
52
+ depends_on: string[];
53
+ payload_url: string;
54
+ assignee?: string;
55
+ resolve_on?: ResolveOn;
56
+ dedup_key?: string;
57
+ claimed_by?: string;
58
+ lease_expires_at?: string;
59
+ due_at?: string;
60
+ fail_reason?: string;
61
+ resolved_at?: string;
62
+ created_at: string;
63
+ updated_at: string;
64
+ }
65
+
66
+ // Backlog health from the list envelope's meta — the agent's "am I keeping
67
+ // up" signal. `shown` is how many rows came back; `total_open` is the whole
68
+ // open queue depth.
69
+ export interface TaskListMeta {
70
+ shown: number;
71
+ total_open: number;
72
+ }
73
+
74
+ export interface TaskListResult {
75
+ tasks: TaskRef[];
76
+ meta: TaskListMeta;
77
+ }
78
+
79
+ export interface CreateTaskOptions {
80
+ description: string;
81
+ // Full Markdown context. Staged to the body tier; read once via getTaskBody.
82
+ body?: string;
83
+ importance?: TaskImportance;
84
+ dueAt?: string;
85
+ assignee?: string;
86
+ tags?: string[];
87
+ // Task ids this task waits on. Immutable — declared only at creation.
88
+ // Same shape as queue.enqueue's depends_on.
89
+ dependsOn?: string[];
90
+ dedupKey?: string;
91
+ resolveOn?: ResolveOn;
92
+ source?: string;
93
+ }
94
+
95
+ export interface ListTaskOptions {
96
+ status?: string;
97
+ tag?: string;
98
+ importance?: string;
99
+ assignee?: string;
100
+ source?: string;
101
+ limit?: number;
102
+ }
103
+
104
+ function tasksBase(orgId: string): string {
105
+ return `${BASE_URL}/task/orgs/${encodeURIComponent(orgId)}/tasks`;
106
+ }
107
+
108
+ function taskBase(orgId: string, id: string): string {
109
+ return `${tasksBase(orgId)}/${encodeURIComponent(id)}`;
110
+ }
111
+
112
+ // createTask is idempotent on (org_id, dedup_key). A task with unresolved
113
+ // depends_on starts blocked; one with an assignee emails them a magic link.
114
+ export async function createTask(apiKey: string, orgId: string, opts: CreateTaskOptions): Promise<Task> {
115
+ const body: Record<string, unknown> = { description: opts.description };
116
+ if (opts.body !== undefined) body.body = opts.body;
117
+ if (opts.importance !== undefined) body.importance = opts.importance;
118
+ if (opts.dueAt !== undefined) body.due_at = opts.dueAt;
119
+ if (opts.assignee !== undefined) body.assignee = opts.assignee;
120
+ if (opts.tags !== undefined) body.tags = opts.tags;
121
+ if (opts.dependsOn !== undefined) body.depends_on = opts.dependsOn;
122
+ if (opts.dedupKey !== undefined) body.dedup_key = opts.dedupKey;
123
+ if (opts.resolveOn !== undefined) body.resolve_on = opts.resolveOn;
124
+ if (opts.source !== undefined) body.source = opts.source;
125
+ return request('POST', tasksBase(orgId), apiKey, body);
126
+ }
127
+
128
+ // listTasks returns the ranked open queue (projected to TaskRef) plus the
129
+ // backlog counts. Default: top 20 open tasks by score. The backend nests
130
+ // {shown, total_open} inside the response `data.meta` (not the envelope meta).
131
+ export async function listTasks(apiKey: string, orgId: string, opts: ListTaskOptions = {}): Promise<TaskListResult> {
132
+ const q = new URLSearchParams();
133
+ if (opts.status) q.set('status', opts.status);
134
+ if (opts.tag) q.set('tag', opts.tag);
135
+ if (opts.importance) q.set('importance', opts.importance);
136
+ if (opts.assignee) q.set('assignee', opts.assignee);
137
+ if (opts.source) q.set('source', opts.source);
138
+ if (opts.limit !== undefined) q.set('limit', String(opts.limit));
139
+ const qs = q.toString();
140
+ const res = await request<{ tasks?: TaskRef[]; meta?: Partial<TaskListMeta> }>(
141
+ 'GET', `${tasksBase(orgId)}${qs ? `?${qs}` : ''}`, apiKey,
142
+ );
143
+ const tasks = res?.tasks ?? [];
144
+ const m = res?.meta ?? {};
145
+ return {
146
+ tasks,
147
+ meta: {
148
+ shown: typeof m.shown === 'number' ? m.shown : tasks.length,
149
+ total_open: typeof m.total_open === 'number' ? m.total_open : tasks.length,
150
+ },
151
+ };
152
+ }
153
+
154
+ // getTask returns the full task object. It deliberately does NOT fetch the
155
+ // Markdown body tier — call getTaskBody for that, once, on commit.
156
+ export async function getTask(apiKey: string, orgId: string, id: string): Promise<Task> {
157
+ return request('GET', taskBase(orgId, id), apiKey);
158
+ }
159
+
160
+ // getTaskBody fetches the body tier — the full Markdown context. Separate
161
+ // call by design: it keeps the list/get path token-cheap. The endpoint
162
+ // returns { task_id, body }.
163
+ export async function getTaskBody(apiKey: string, orgId: string, id: string): Promise<string> {
164
+ const res = await request<{ task_id: string; body: string }>('GET', `${taskBase(orgId, id)}/body`, apiKey);
165
+ return res?.body ?? '';
166
+ }
167
+
168
+ // claimTask takes an atomic lease (default 10 min) and returns the full
169
+ // updated task — lease state is on `lease_expires_at` / `claimed_by`.
170
+ export async function claimTask(apiKey: string, orgId: string, id: string, opts: { leaseSeconds?: number; worker?: string } = {}): Promise<Task> {
171
+ const body: Record<string, unknown> = {};
172
+ if (opts.leaseSeconds !== undefined) body.lease_seconds = opts.leaseSeconds;
173
+ if (opts.worker !== undefined) body.worker = opts.worker;
174
+ return request('POST', `${taskBase(orgId, id)}/claim`, apiKey, body);
175
+ }
176
+
177
+ // extendTask is a heartbeat for long work — extends a live claim.
178
+ export async function extendTask(apiKey: string, orgId: string, id: string, opts: { leaseSeconds?: number } = {}): Promise<Task> {
179
+ const body: Record<string, unknown> = {};
180
+ if (opts.leaseSeconds !== undefined) body.lease_seconds = opts.leaseSeconds;
181
+ return request('POST', `${taskBase(orgId, id)}/extend`, apiKey, body);
182
+ }
183
+
184
+ // resolveTask resolves a task (terminal) — unblocks any dependents.
185
+ export async function resolveTask(apiKey: string, orgId: string, id: string): Promise<Task> {
186
+ return request('POST', `${taskBase(orgId, id)}/resolve`, apiKey, {});
187
+ }
188
+
189
+ // failTask fails a task (terminal, with a reason). Does not auto-retry;
190
+ // dependents that can never proceed are auto-failed.
191
+ export async function failTask(apiKey: string, orgId: string, id: string, reason: string): Promise<Task> {
192
+ return request('POST', `${taskBase(orgId, id)}/fail`, apiKey, { reason });
193
+ }
194
+
195
+ // cancelTask cancels a task (terminal, distinct from fail).
196
+ export async function cancelTask(apiKey: string, orgId: string, id: string): Promise<void> {
197
+ return request('DELETE', taskBase(orgId, id), apiKey);
198
+ }
package/src/workflow.ts CHANGED
@@ -33,7 +33,13 @@ export type WorkflowStep =
33
33
  // Backend (2026-05-15): `http_request` step is now accepted (alias: `http`).
34
34
  // POSTs (or other method) to `url` with the inbound payload; supports
35
35
  // template substitution in url/body the same way email steps do.
36
- | { type: 'http_request' | 'http'; url: string; method?: 'GET' | 'POST' | 'PATCH' | 'PUT' | 'DELETE'; body?: string; headers?: Record<string, string> };
36
+ | { type: 'http_request' | 'http'; url: string; method?: 'GET' | 'POST' | 'PATCH' | 'PUT' | 'DELETE'; body?: string; headers?: Record<string, string> }
37
+ // `enqueue_job` (alias: `enqueue`) hands durable work to my-queue-api —
38
+ // the step enqueues a job on `queue` carrying `payload` (template
39
+ // substitution supported, same as email/http steps). The workflow stays
40
+ // the trigger layer; the queue owns retry/durability. Backend: pending —
41
+ // see docs/cross-repo-prompts/backend-orchestration-composition.md.
42
+ | { type: 'enqueue_job' | 'enqueue'; queue: string; payload?: string; dedup_key?: string; delay_seconds?: number };
37
43
 
38
44
  export interface Workflow {
39
45
  id: string;