@mortar-ai/skill 0.5.0 → 0.7.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.
@@ -0,0 +1,2942 @@
1
+ openapi: 3.0.3
2
+ info:
3
+ title: Mortar REST API
4
+ version: "0.2.1"
5
+ description: |
6
+ Backend-as-a-Service for mainland-China-friendly mobile and web apps.
7
+
8
+ This file is authoritative for every operation, request, response, error,
9
+ security rule, and trust-surface extension it documents, but it is not yet
10
+ an exhaustive route inventory. `internal/api/routes.go` remains authoritative
11
+ for route existence. AI code generation must use documented operations and
12
+ must not invent a missing contract merely because a route exists in code.
13
+
14
+ Project-scope paths in this document use the internal and backward-
15
+ compatible `/v1/{tenant}/...` form so generic OpenAPI tooling can supply a
16
+ path parameter on the apex server. The current SDK contract instead puts
17
+ the project UUID in the host and omits that path segment:
18
+ `https://<tenant>.api.mortar.appunvs.com/v1/...`. Do not combine both forms.
19
+
20
+ Two authentication schemes coexist:
21
+ * `AccountToken` — Account session JWT minted by
22
+ `/v1/_accounts/signin`, or an Account PAT (`mtr_pat_*`); scopes all
23
+ `/v1/_accounts/*` and project-lifecycle calls to one Mortar account.
24
+ * `APIKey` — Bearer token starting with `mtr_live_` (production) or
25
+ `mtr_test_` (sandbox); scopes a single project (its tenant_id) and one of
26
+ `admin` / `app` / `public` privilege tiers.
27
+
28
+ Signed-in project users may additionally send a User access JWT in
29
+ `X-Mortar-User-Token`. That optional identity layer is not modeled
30
+ consistently by every operation in this partial document.
31
+
32
+ Errors use a uniform `{ "error": "<machine_code>" }` body, occasionally
33
+ enriched with structured detail (see `tier_change_would_overflow`).
34
+ contact:
35
+ name: Mortar team
36
+ url: https://mortar.appunvs.com
37
+ servers:
38
+ - url: https://api.mortar.appunvs.com
39
+ description: Production apex for account routes and path-form project examples in this reference
40
+ - url: http://localhost:8080
41
+ description: Local development apex
42
+
43
+ x-mortar-ai-contract:
44
+ projectSourceOwner: fabric-harness-ai-turn
45
+ generatedSourceEndpoint: forbidden
46
+ defaultProjectCodeAccess: deny
47
+ description: |
48
+ Mortar publishes API contracts and live schema; it does not generate source
49
+ files for Fabric projects. Harness exposes only the operations relevant to a
50
+ turn, and the AI writes runtime-specific request, response, and application
51
+ types into ordinary project source. Operations marked projectCode: forbidden
52
+ are control-plane calls and must be reached only through their Harness tool.
53
+
54
+ tags:
55
+ - name: accounts
56
+ description: Mortar-account signup / signin / profile + project lifecycle.
57
+ - name: projects
58
+ description: Account-scope CRUD over the projects an account owns.
59
+ - name: auth
60
+ description: Per-project User registration and sessions.
61
+ - name: data
62
+ description: Per-project key/value tables + rows.
63
+ - name: storage
64
+ description: Per-project blob storage with optional signed-URL downloads.
65
+ - name: realtime
66
+ description: Server-Sent-Events stream of row changes for a given table.
67
+ - name: usage
68
+ description: Project credit balance + compute-spec caps surfaced by `mortar usage me` and embedded usage widgets.
69
+ - name: compute
70
+ description: Per-project serverless functions (deploy / delete / invoke / logs).
71
+ - name: payment
72
+ description: Top-up flow + provider webhooks (WeChat Pay, Alipay).
73
+ - name: admin
74
+ description: Project-scope admin operations (currently API key minting).
75
+ - name: health
76
+ description: Liveness / readiness / status probes (no auth).
77
+ - name: cron
78
+ description: Per-project scheduled function invocations.
79
+ - name: queue
80
+ description: Durable background-job queue (Postgres-backed).
81
+ - name: comms
82
+ description: Messaging primitive — transactional email + SMS, billed per message per channel.
83
+ - name: tokens
84
+ description: Account-scope Personal Access Tokens (PATs, format `mtr_pat_*`) for CI / headless use.
85
+ - name: domains
86
+ description: Account-scoped hostname claim and DNS verification only; custom-host routing and TLS provisioning are not implemented.
87
+
88
+ security:
89
+ - APIKey: []
90
+ - AccountToken: []
91
+
92
+ paths:
93
+ /health:
94
+ get:
95
+ tags: [health]
96
+ summary: Combined liveness + readiness probe.
97
+ security: []
98
+ responses:
99
+ "200":
100
+ description: Service is up.
101
+ "503":
102
+ description: A required dependency (DB or Redis) is unreachable.
103
+
104
+ /health/live:
105
+ get:
106
+ tags: [health]
107
+ summary: Process-level liveness — returns 200 if the binary can serve.
108
+ security: []
109
+ responses:
110
+ "200":
111
+ description: Alive.
112
+
113
+ /health/ready:
114
+ get:
115
+ tags: [health]
116
+ summary: Readiness — fails when DB or Redis aren't reachable.
117
+ security: []
118
+ responses:
119
+ "200":
120
+ description: Ready to serve.
121
+ "503":
122
+ description: Not ready.
123
+
124
+ /status:
125
+ get:
126
+ tags: [health]
127
+ summary: Human-readable HTML status page.
128
+ security: []
129
+ responses:
130
+ "200":
131
+ description: HTML status page.
132
+
133
+ /v1/_accounts/signup:
134
+ post:
135
+ tags: [accounts]
136
+ summary: Create a new Mortar account.
137
+ security: []
138
+ requestBody:
139
+ required: true
140
+ content:
141
+ application/json:
142
+ schema:
143
+ $ref: "#/components/schemas/AccountSignupRequest"
144
+ responses:
145
+ "200":
146
+ description: Account created; JWT issued.
147
+ content:
148
+ application/json:
149
+ schema:
150
+ $ref: "#/components/schemas/AccountTokenResponse"
151
+ "400":
152
+ $ref: "#/components/responses/BadRequest"
153
+ "409":
154
+ description: email_taken.
155
+ content:
156
+ application/json:
157
+ schema:
158
+ $ref: "#/components/schemas/Error"
159
+
160
+ /v1/_accounts/signin:
161
+ post:
162
+ tags: [accounts]
163
+ summary: Sign in to an existing Mortar account.
164
+ security: []
165
+ requestBody:
166
+ required: true
167
+ content:
168
+ application/json:
169
+ schema:
170
+ $ref: "#/components/schemas/AccountSigninRequest"
171
+ examples:
172
+ standard:
173
+ value:
174
+ email: dev@example.com
175
+ password: hunter2hunter2
176
+ responses:
177
+ "200":
178
+ description: Signed in.
179
+ content:
180
+ application/json:
181
+ schema:
182
+ $ref: "#/components/schemas/AccountTokenResponse"
183
+ examples:
184
+ ok:
185
+ value:
186
+ token: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...
187
+ expires_at: "2026-05-20T10:30:00Z"
188
+ account:
189
+ id: acct_01HZ4
190
+ email: dev@example.com
191
+ name: Dev User
192
+ created_at: "2026-04-12T09:11:00Z"
193
+ "401":
194
+ description: invalid_credentials.
195
+ "403":
196
+ description: suspended.
197
+
198
+ /v1/_accounts/me:
199
+ get:
200
+ tags: [accounts]
201
+ summary: Return the current account's profile.
202
+ security:
203
+ - AccountToken: []
204
+ responses:
205
+ "200":
206
+ description: Profile.
207
+ content:
208
+ application/json:
209
+ schema:
210
+ $ref: "#/components/schemas/Account"
211
+ "401":
212
+ description: unauthorized.
213
+ "404":
214
+ description: not_found (account row missing).
215
+
216
+ /v1/_accounts/enterprise/contract:
217
+ get:
218
+ tags: [accounts]
219
+ summary: Return the authenticated Enterprise customer's logical capacity contract.
220
+ description: Physical cells, nodes, utilization, provider evidence, raw Mortar COGS, and publication operations are intentionally excluded.
221
+ security:
222
+ - AccountToken: []
223
+ responses:
224
+ "200":
225
+ description: Logical profiles, customer contract rates, and current admission availability.
226
+ content:
227
+ application/json:
228
+ schema:
229
+ $ref: "#/components/schemas/EnterpriseContract"
230
+ "403":
231
+ description: enterprise_subscription_required.
232
+
233
+ /v1/_accounts/notification-email/start:
234
+ post:
235
+ tags: [accounts]
236
+ summary: Send a verification code to an operational notification email.
237
+ description: The verified address receives account credential-expiry reminders; it does not replace the login email.
238
+ security:
239
+ - AccountToken: []
240
+ requestBody:
241
+ required: true
242
+ content:
243
+ application/json:
244
+ schema:
245
+ type: object
246
+ required: [email]
247
+ properties:
248
+ email:
249
+ type: string
250
+ format: email
251
+ responses:
252
+ "200":
253
+ description: Verification message accepted by the configured email provider.
254
+ "400":
255
+ description: invalid_body / invalid_email.
256
+ "429":
257
+ description: Verification send rate limit exceeded.
258
+ "502":
259
+ description: notification_email_delivery_failed.
260
+ "503":
261
+ description: notification_email_not_configured.
262
+
263
+ /v1/_accounts/notification-email/verify:
264
+ post:
265
+ tags: [accounts]
266
+ summary: Verify and bind the account's operational notification email.
267
+ security:
268
+ - AccountToken: []
269
+ requestBody:
270
+ required: true
271
+ content:
272
+ application/json:
273
+ schema:
274
+ type: object
275
+ required: [email, code]
276
+ properties:
277
+ email:
278
+ type: string
279
+ format: email
280
+ code:
281
+ type: string
282
+ pattern: "^[0-9]{6}$"
283
+ responses:
284
+ "200":
285
+ description: Address verified and bound.
286
+ "400":
287
+ description: invalid_verification_code / verification_code_expired.
288
+ "429":
289
+ description: verification_attempts_exhausted.
290
+ "503":
291
+ description: notification_email_not_configured.
292
+
293
+ /v1/_accounts/projects:
294
+ post:
295
+ tags: [projects]
296
+ summary: Create a new project.
297
+ security:
298
+ - AccountToken: []
299
+ requestBody:
300
+ required: true
301
+ content:
302
+ application/json:
303
+ schema:
304
+ $ref: "#/components/schemas/ProjectCreateRequest"
305
+ examples:
306
+ subscription_default:
307
+ value:
308
+ name: My App
309
+ credit_mode: hard_budget
310
+ responses:
311
+ "201":
312
+ description: Created.
313
+ content:
314
+ application/json:
315
+ schema:
316
+ $ref: "#/components/schemas/Project"
317
+ examples:
318
+ created:
319
+ value:
320
+ id: proj_01HZ4ABC
321
+ name: My App
322
+ compute_spec: nano
323
+ tier: nano
324
+ credit_mode: hard_budget
325
+ created_at: "2026-05-13T10:00:00Z"
326
+ "400":
327
+ $ref: "#/components/responses/BadRequest"
328
+ "401":
329
+ description: unauthorized.
330
+ "403":
331
+ description: compute_spec_not_available or active_project_limit_reached for the account subscription.
332
+ get:
333
+ tags: [projects]
334
+ summary: List the account's projects, newest first.
335
+ security:
336
+ - AccountToken: []
337
+ responses:
338
+ "200":
339
+ description: Project list.
340
+ content:
341
+ application/json:
342
+ schema:
343
+ type: object
344
+ properties:
345
+ projects:
346
+ type: array
347
+ items:
348
+ $ref: "#/components/schemas/Project"
349
+ "401":
350
+ description: unauthorized.
351
+
352
+ /v1/_accounts/projects/{id}:
353
+ parameters:
354
+ - $ref: "#/components/parameters/ProjectIDPath"
355
+ get:
356
+ tags: [projects]
357
+ summary: Fetch a single project by id.
358
+ security:
359
+ - AccountToken: []
360
+ responses:
361
+ "200":
362
+ description: Project.
363
+ content:
364
+ application/json:
365
+ schema:
366
+ $ref: "#/components/schemas/Project"
367
+ "404":
368
+ description: not_found (also returned for cross-account reads).
369
+ patch:
370
+ tags: [projects]
371
+ summary: Partial update (name, compute_spec, credit_mode, or Enterprise capacity_profile).
372
+ security:
373
+ - AccountToken: []
374
+ requestBody:
375
+ required: true
376
+ content:
377
+ application/json:
378
+ schema:
379
+ $ref: "#/components/schemas/ProjectUpdateRequest"
380
+ responses:
381
+ "200":
382
+ description: Updated.
383
+ content:
384
+ application/json:
385
+ schema:
386
+ $ref: "#/components/schemas/Project"
387
+ "400":
388
+ $ref: "#/components/responses/BadRequest"
389
+ "404":
390
+ description: not_found.
391
+ "409":
392
+ description: tier_change_would_overflow — see structured blockers.
393
+ content:
394
+ application/json:
395
+ schema:
396
+ $ref: "#/components/schemas/TierOverflow"
397
+ delete:
398
+ tags: [projects]
399
+ summary: Soft-delete a project (30-day retention).
400
+ security:
401
+ - AccountToken: []
402
+ responses:
403
+ "204":
404
+ description: Deleted.
405
+ "404":
406
+ description: not_found.
407
+
408
+ /v1/_accounts/projects/{id}/write-suspension:
409
+ parameters:
410
+ - $ref: "#/components/parameters/ProjectIDPath"
411
+ put:
412
+ tags: [projects]
413
+ summary: Suspend or resume tenant mutations for an owned Enterprise capacity-profile project.
414
+ security:
415
+ - AccountToken: []
416
+ requestBody:
417
+ required: true
418
+ content:
419
+ application/json:
420
+ schema:
421
+ $ref: "#/components/schemas/ProjectWriteSuspensionRequest"
422
+ responses:
423
+ "200":
424
+ description: Write state updated or already converged.
425
+ content:
426
+ application/json:
427
+ schema:
428
+ $ref: "#/components/schemas/ProjectWriteSuspension"
429
+ "400":
430
+ $ref: "#/components/responses/BadRequest"
431
+ "403":
432
+ description: enterprise_capacity_project_required.
433
+ "404":
434
+ description: not_found.
435
+
436
+ /v1/_accounts/projects/{id}/topup:
437
+ parameters:
438
+ - $ref: "#/components/parameters/ProjectIDPath"
439
+ post:
440
+ tags: [payment]
441
+ summary: Initiate a manual top-up via WeChat Pay or Alipay.
442
+ security:
443
+ - AccountToken: []
444
+ requestBody:
445
+ required: true
446
+ content:
447
+ application/json:
448
+ schema:
449
+ $ref: "#/components/schemas/TopupRequest"
450
+ examples:
451
+ wechat_50:
452
+ value:
453
+ provider: wechat_pay
454
+ amount_yuan: 50.0
455
+ responses:
456
+ "200":
457
+ description: Charge created — render QR or redirect the browser.
458
+ content:
459
+ application/json:
460
+ schema:
461
+ $ref: "#/components/schemas/ChargeResult"
462
+ examples:
463
+ wechat:
464
+ value:
465
+ provider: wechat_pay
466
+ external_ref: a1b2c3d4e5f6
467
+ qr_code_data: weixin://wxpay/bizpayurl?pr=AbCdEfG
468
+ expires_at_unix_sec: 1747130000
469
+ "400":
470
+ description: invalid_body / invalid_provider / amount_out_of_range.
471
+ "404":
472
+ description: not_found.
473
+ "502":
474
+ description: provider_call_failed.
475
+ "503":
476
+ description: provider_unavailable (no provider configured).
477
+
478
+ /v1/_webhooks/payment/{provider}:
479
+ parameters:
480
+ - name: provider
481
+ in: path
482
+ required: true
483
+ schema:
484
+ type: string
485
+ enum: [wechat_pay, alipay]
486
+ post:
487
+ tags: [payment]
488
+ summary: Provider webhook — signature-verified inline, no other auth.
489
+ security: []
490
+ requestBody:
491
+ required: true
492
+ content:
493
+ application/json:
494
+ schema:
495
+ $ref: "#/components/schemas/WebhookEvent"
496
+ responses:
497
+ "200":
498
+ description: Event accepted (idempotent; duplicates are no-ops).
499
+ "400":
500
+ description: verify_failed / no_project_id / read_body.
501
+ "404":
502
+ description: unknown_provider.
503
+
504
+ /v1/{tenant}/_admin/keys:
505
+ parameters:
506
+ - $ref: "#/components/parameters/TenantPath"
507
+ post:
508
+ tags: [admin]
509
+ summary: Mint a new API key for the project (admin scope required).
510
+ security:
511
+ - APIKey: []
512
+ requestBody:
513
+ required: true
514
+ content:
515
+ application/json:
516
+ schema:
517
+ $ref: "#/components/schemas/APIKeyCreateRequest"
518
+ responses:
519
+ "201":
520
+ description: Key created — `raw_key` returned exactly once.
521
+ content:
522
+ application/json:
523
+ schema:
524
+ $ref: "#/components/schemas/APIKey"
525
+ "400":
526
+ description: invalid_body / name_required / invalid_scope.
527
+ "403":
528
+ description: admin_scope_required.
529
+
530
+ /v1/{tenant}/auth/signup:
531
+ parameters:
532
+ - $ref: "#/components/parameters/TenantPath"
533
+ post:
534
+ tags: [auth]
535
+ summary: Create a User inside this project.
536
+ security:
537
+ - APIKey: []
538
+ requestBody:
539
+ required: true
540
+ content:
541
+ application/json:
542
+ schema:
543
+ $ref: "#/components/schemas/UserAuthRequest"
544
+ responses:
545
+ "201":
546
+ description: User created.
547
+ content:
548
+ application/json:
549
+ schema:
550
+ $ref: "#/components/schemas/UserAuthResponse"
551
+ "400":
552
+ description: invalid_body / email_invalid / password_too_short.
553
+ "409":
554
+ description: email_taken.
555
+
556
+ /v1/{tenant}/auth/signin:
557
+ parameters:
558
+ - $ref: "#/components/parameters/TenantPath"
559
+ post:
560
+ tags: [auth]
561
+ summary: Sign in as a User.
562
+ security:
563
+ - APIKey: []
564
+ requestBody:
565
+ required: true
566
+ content:
567
+ application/json:
568
+ schema:
569
+ $ref: "#/components/schemas/UserAuthRequest"
570
+ responses:
571
+ "200":
572
+ description: Signed in.
573
+ content:
574
+ application/json:
575
+ schema:
576
+ $ref: "#/components/schemas/UserAuthResponse"
577
+ "401":
578
+ description: invalid_credentials.
579
+
580
+ /v1/{tenant}/auth/phone/start:
581
+ parameters:
582
+ - $ref: "#/components/parameters/TenantPath"
583
+ post:
584
+ tags: [auth]
585
+ summary: Start phone-OTP login — cache a 6-digit code + send via comms.sms.
586
+ description: |
587
+ Composes the `comms` (sms) + `cache` primitives. Send-side throttle
588
+ is enforced (60s cooldown per recipient, 10/day per recipient,
589
+ 200/day per tenant — see auth.md). Returns 200 even if the phone
590
+ is new — phone-OTP find-or-creates the User on verify.
591
+ security:
592
+ - APIKey: []
593
+ requestBody:
594
+ required: true
595
+ content:
596
+ application/json:
597
+ schema:
598
+ type: object
599
+ required: [phone]
600
+ properties:
601
+ phone:
602
+ type: string
603
+ description: E.164 phone (e.g. `+8613800138000`).
604
+ responses:
605
+ "200":
606
+ description: Code cached + SMS handed to the comms backend.
607
+ content:
608
+ application/json:
609
+ schema:
610
+ type: object
611
+ properties:
612
+ sent:
613
+ type: boolean
614
+ expires_in_sec:
615
+ type: integer
616
+ "400":
617
+ description: phone_invalid / invalid_body.
618
+ "429":
619
+ description: cooldown / daily_cap_recipient / daily_cap_tenant.
620
+ "503":
621
+ description: comms_not_configured.
622
+
623
+ /v1/{tenant}/auth/phone/verify:
624
+ parameters:
625
+ - $ref: "#/components/parameters/TenantPath"
626
+ post:
627
+ tags: [auth]
628
+ summary: Verify the SMS code — find-or-create a User by phone, mint JWT.
629
+ security:
630
+ - APIKey: []
631
+ requestBody:
632
+ required: true
633
+ content:
634
+ application/json:
635
+ schema:
636
+ type: object
637
+ required: [phone, code]
638
+ properties:
639
+ phone:
640
+ type: string
641
+ code:
642
+ type: string
643
+ responses:
644
+ "200":
645
+ description: Code matched. User found-or-created, JWT issued.
646
+ content:
647
+ application/json:
648
+ schema:
649
+ $ref: "#/components/schemas/UserAuthResponse"
650
+ "401":
651
+ description: invalid_code (wrong or expired).
652
+ "429":
653
+ description: too_many_attempts (verify attempt cap hit — code invalidated).
654
+
655
+ /v1/{tenant}/auth/email/verify/start:
656
+ parameters:
657
+ - $ref: "#/components/parameters/TenantPath"
658
+ post:
659
+ tags: [auth]
660
+ summary: Email a verification token to an existing User.
661
+ description: |
662
+ Always returns 200 — never reveals whether the email is registered.
663
+ Token TTL 15 min. Send-side throttle as above.
664
+ security:
665
+ - APIKey: []
666
+ requestBody:
667
+ required: true
668
+ content:
669
+ application/json:
670
+ schema:
671
+ type: object
672
+ required: [email]
673
+ properties:
674
+ email:
675
+ type: string
676
+ responses:
677
+ "200":
678
+ description: Sent (or silently no-op if no user).
679
+ "429":
680
+ description: cooldown / daily caps.
681
+
682
+ /v1/{tenant}/auth/email/verify/confirm:
683
+ parameters:
684
+ - $ref: "#/components/parameters/TenantPath"
685
+ post:
686
+ tags: [auth]
687
+ summary: Confirm the token, set `email_verified=true` on the User.
688
+ security:
689
+ - APIKey: []
690
+ requestBody:
691
+ required: true
692
+ content:
693
+ application/json:
694
+ schema:
695
+ type: object
696
+ required: [email, token]
697
+ properties:
698
+ email:
699
+ type: string
700
+ token:
701
+ type: string
702
+ responses:
703
+ "200":
704
+ description: Marked verified (idempotent).
705
+ "401":
706
+ description: invalid_token.
707
+ "429":
708
+ description: too_many_attempts.
709
+
710
+ /v1/{tenant}/auth/password/reset/start:
711
+ parameters:
712
+ - $ref: "#/components/parameters/TenantPath"
713
+ post:
714
+ tags: [auth]
715
+ summary: Email a password-reset token to an existing User.
716
+ description: |
717
+ Always returns 200 (no enumeration). Token TTL 30 min.
718
+ security:
719
+ - APIKey: []
720
+ requestBody:
721
+ required: true
722
+ content:
723
+ application/json:
724
+ schema:
725
+ type: object
726
+ required: [email]
727
+ properties:
728
+ email:
729
+ type: string
730
+ responses:
731
+ "200":
732
+ description: Sent (or silently no-op if no user).
733
+ "429":
734
+ description: cooldown / daily caps.
735
+
736
+ /v1/{tenant}/auth/password/reset/confirm:
737
+ parameters:
738
+ - $ref: "#/components/parameters/TenantPath"
739
+ post:
740
+ tags: [auth]
741
+ summary: Consume the reset token, update the User's password hash.
742
+ security:
743
+ - APIKey: []
744
+ requestBody:
745
+ required: true
746
+ content:
747
+ application/json:
748
+ schema:
749
+ type: object
750
+ required: [email, token, new_password]
751
+ properties:
752
+ email:
753
+ type: string
754
+ token:
755
+ type: string
756
+ new_password:
757
+ type: string
758
+ description: New password (>=8 chars).
759
+ responses:
760
+ "200":
761
+ description: Password updated.
762
+ "400":
763
+ description: password_too_short / email_or_token_invalid.
764
+ "401":
765
+ description: invalid_token.
766
+
767
+ /v1/{tenant}/comms/send:
768
+ parameters:
769
+ - $ref: "#/components/parameters/TenantPath"
770
+ post:
771
+ tags: [comms]
772
+ summary: Send one transactional message (email or sms) via the comms primitive.
773
+ description: |
774
+ Billed per message on the channel's resource (`email` or `sms`) in
775
+ the credit ledger. Aliyun SMS backends require a pre-registered
776
+ `template` code (模板报备).
777
+ security:
778
+ - APIKey: []
779
+ requestBody:
780
+ required: true
781
+ content:
782
+ application/json:
783
+ schema:
784
+ type: object
785
+ required: [channel, to]
786
+ properties:
787
+ channel:
788
+ type: string
789
+ enum: [email, sms]
790
+ to:
791
+ type: string
792
+ description: Email address (channel=email) or E.164 phone (channel=sms).
793
+ subject:
794
+ type: string
795
+ description: Email only.
796
+ body:
797
+ type: string
798
+ template:
799
+ type: string
800
+ description: Provider template code (REQUIRED by real SMS providers).
801
+ vars:
802
+ type: object
803
+ additionalProperties:
804
+ type: string
805
+ responses:
806
+ "201":
807
+ description: Accepted by the backend.
808
+ content:
809
+ application/json:
810
+ schema:
811
+ type: object
812
+ properties:
813
+ id:
814
+ type: string
815
+ channel:
816
+ type: string
817
+ to:
818
+ type: string
819
+ provider:
820
+ type: string
821
+ enum: [local, aliyun, tencent]
822
+ "400":
823
+ description: invalid_body / invalid_message.
824
+ "503":
825
+ description: comms_not_configured.
826
+
827
+ /v1/{tenant}/db/tables:
828
+ parameters:
829
+ - $ref: "#/components/parameters/TenantPath"
830
+ post:
831
+ tags: [data]
832
+ operationId: createDatabaseTableLegacy
833
+ x-mortar-trust-surface: harness-control
834
+ x-mortar-project-code: forbidden
835
+ summary: Create a table.
836
+ security:
837
+ - APIKey: []
838
+ requestBody:
839
+ required: true
840
+ content:
841
+ application/json:
842
+ schema:
843
+ $ref: "#/components/schemas/TableCreateRequest"
844
+ responses:
845
+ "201":
846
+ description: Table created.
847
+ content:
848
+ application/json:
849
+ schema:
850
+ $ref: "#/components/schemas/Table"
851
+ "400":
852
+ description: invalid_body / name_required / column_name_required.
853
+ "409":
854
+ description: table_exists.
855
+ get:
856
+ tags: [data]
857
+ operationId: listDatabaseTables
858
+ x-mortar-trust-surface: app-runtime
859
+ x-mortar-project-code: allowed
860
+ summary: List tables in this project.
861
+ security:
862
+ - APIKey: []
863
+ responses:
864
+ "200":
865
+ description: Tables.
866
+ content:
867
+ application/json:
868
+ schema:
869
+ type: object
870
+ properties:
871
+ tables:
872
+ type: array
873
+ items:
874
+ $ref: "#/components/schemas/Table"
875
+
876
+ /v1/{tenant}/db/migrations/plan:
877
+ parameters:
878
+ - $ref: "#/components/parameters/TenantPath"
879
+ post:
880
+ tags: [data]
881
+ operationId: planDatabaseMigration
882
+ x-mortar-trust-surface: harness-control
883
+ x-mortar-project-code: forbidden
884
+ summary: Parse exact versioned SQL into a typed execution plan without mutation.
885
+ description: |
886
+ Harness-only control operation behind `mortar_migrate`. Application code
887
+ must not call this endpoint or embed an app/admin credential for it.
888
+ security:
889
+ - APIKey: []
890
+ requestBody:
891
+ required: true
892
+ content:
893
+ application/json:
894
+ schema:
895
+ $ref: "#/components/schemas/MigrationPlanRequest"
896
+ responses:
897
+ "200":
898
+ description: Language-neutral plan, diagnostics, exact checksum, and current tenant schema revision.
899
+ content:
900
+ application/json:
901
+ schema:
902
+ $ref: "#/components/schemas/MigrationPlan"
903
+ "409":
904
+ description: The migration id is already recorded with different exact SQL bytes.
905
+
906
+ /v1/{tenant}/db/migrations/apply:
907
+ parameters:
908
+ - $ref: "#/components/parameters/TenantPath"
909
+ post:
910
+ tags: [data]
911
+ operationId: applyDatabaseMigration
912
+ x-mortar-trust-surface: harness-control
913
+ x-mortar-project-code: forbidden
914
+ summary: Atomically apply a reviewed exact-SQL migration and record its revision.
915
+ description: |
916
+ Re-parses the exact SQL, verifies SHA-256 and expected_revision, locks
917
+ this tenant's schema revision, then commits every logical schema
918
+ operation and the ledger row in one PostgreSQL transaction. Retrying an
919
+ already-recorded migration_id + checksum is idempotent.
920
+ security:
921
+ - APIKey: []
922
+ requestBody:
923
+ required: true
924
+ content:
925
+ application/json:
926
+ schema:
927
+ $ref: "#/components/schemas/MigrationApplyRequest"
928
+ responses:
929
+ "200":
930
+ description: Applied or idempotently confirmed.
931
+ content:
932
+ application/json:
933
+ schema:
934
+ $ref: "#/components/schemas/MigrationApplyResponse"
935
+ "409":
936
+ description: Checksum or expected schema revision conflict.
937
+ "422":
938
+ description: The typed plan is not executable.
939
+
940
+ /v1/{tenant}/db/tables/{table}:
941
+ parameters:
942
+ - $ref: "#/components/parameters/TenantPath"
943
+ - $ref: "#/components/parameters/TablePath"
944
+ delete:
945
+ tags: [data]
946
+ operationId: dropDatabaseTableLegacy
947
+ x-mortar-trust-surface: harness-control
948
+ x-mortar-project-code: forbidden
949
+ summary: Drop the table.
950
+ security:
951
+ - APIKey: []
952
+ responses:
953
+ "204":
954
+ description: Dropped.
955
+
956
+ /v1/{tenant}/db/transactions:
957
+ parameters:
958
+ - $ref: "#/components/parameters/TenantPath"
959
+ post:
960
+ tags: [data]
961
+ operationId: executeDatabaseTransaction
962
+ x-mortar-trust-surface: trusted-server
963
+ x-mortar-project-code: trusted-server-only
964
+ summary: Atomically execute row writes and durable queue enqueues.
965
+ description: |
966
+ Runs every operation in one tenant-scoped PostgreSQL transaction. A
967
+ failure rolls back all preceding operations, including jobs inserted
968
+ into the PostgreSQL-backed queue. Requires an `app` or `admin` key;
969
+ object storage and other external providers are not part of this ACID
970
+ boundary. Maximum 200 operations and 1 MiB request body.
971
+ security:
972
+ - APIKey: []
973
+ requestBody:
974
+ required: true
975
+ content:
976
+ application/json:
977
+ schema:
978
+ $ref: "#/components/schemas/DBTransactionRequest"
979
+ responses:
980
+ "200":
981
+ description: Every operation committed.
982
+ content:
983
+ application/json:
984
+ schema:
985
+ $ref: "#/components/schemas/DBTransactionResponse"
986
+ "400":
987
+ description: Invalid operation or missing required fields; nothing committed.
988
+ "404":
989
+ description: An update target was not found; nothing committed.
990
+ "503":
991
+ description: transactional_queue_not_configured when an enqueue was requested.
992
+
993
+ /v1/{tenant}/db/tables/{table}/rows:
994
+ parameters:
995
+ - $ref: "#/components/parameters/TenantPath"
996
+ - $ref: "#/components/parameters/TablePath"
997
+ post:
998
+ tags: [data]
999
+ operationId: insertDatabaseRows
1000
+ x-mortar-trust-surface: app-runtime
1001
+ x-mortar-project-code: allowed
1002
+ summary: Atomically insert or upsert one or more rows.
1003
+ description: |
1004
+ A batch is atomic, but this POST is not retry-safe by itself. The API
1005
+ provides no idempotency key or durable uniqueness constraint, and the
1006
+ SDK does not automatically retry it. Client-side in-flight guards only
1007
+ suppress duplicate interaction in that mounted client.
1008
+ security:
1009
+ - APIKey: []
1010
+ parameters:
1011
+ - name: upsert
1012
+ in: query
1013
+ description: When true, rows carrying an `id` replace that row; rows without an `id` insert.
1014
+ schema:
1015
+ type: boolean
1016
+ default: false
1017
+ requestBody:
1018
+ required: true
1019
+ content:
1020
+ application/json:
1021
+ schema:
1022
+ oneOf:
1023
+ - $ref: "#/components/schemas/RowBody"
1024
+ - $ref: "#/components/schemas/RowBatchBody"
1025
+ responses:
1026
+ "201":
1027
+ description: Inserted/upserted. Batch requests are all-or-nothing.
1028
+ content:
1029
+ application/json:
1030
+ schema:
1031
+ oneOf:
1032
+ - $ref: "#/components/schemas/Row"
1033
+ - $ref: "#/components/schemas/RowsResponse"
1034
+ "400":
1035
+ description: invalid_body / table_required / missing_required_fields.
1036
+ content:
1037
+ application/json:
1038
+ schema:
1039
+ $ref: "#/components/schemas/RowValidationError"
1040
+ get:
1041
+ tags: [data]
1042
+ operationId: listDatabaseRows
1043
+ x-mortar-trust-surface: app-runtime
1044
+ x-mortar-project-code: allowed
1045
+ summary: List rows; `?limit=N` (1..200, default 100).
1046
+ security:
1047
+ - APIKey: []
1048
+ parameters:
1049
+ - name: limit
1050
+ in: query
1051
+ schema:
1052
+ type: integer
1053
+ minimum: 1
1054
+ maximum: 200
1055
+ responses:
1056
+ "200":
1057
+ description: Rows.
1058
+ content:
1059
+ application/json:
1060
+ schema:
1061
+ $ref: "#/components/schemas/RowsResponse"
1062
+ patch:
1063
+ tags: [data]
1064
+ operationId: updateDatabaseRows
1065
+ x-mortar-trust-surface: app-runtime
1066
+ x-mortar-project-code: allowed
1067
+ summary: Atomically partial-update rows selected by query filters.
1068
+ description: |
1069
+ Accepts the same PostgREST-style filters, ordering, limit, and offset as
1070
+ GET. Matching rows are locked, partial-merged, validated, and updated in
1071
+ one PostgreSQL transaction (maximum 200 affected rows). Full-text
1072
+ search (`fts`) is rejected on mutation requests.
1073
+ security:
1074
+ - APIKey: []
1075
+ requestBody:
1076
+ required: true
1077
+ content:
1078
+ application/json:
1079
+ schema:
1080
+ $ref: "#/components/schemas/RowBody"
1081
+ responses:
1082
+ "200":
1083
+ description: Updated rows.
1084
+ content:
1085
+ application/json:
1086
+ schema:
1087
+ $ref: "#/components/schemas/RowsResponse"
1088
+ "400":
1089
+ description: Invalid filter/body or missing required fields; nothing updated.
1090
+ delete:
1091
+ tags: [data]
1092
+ operationId: deleteDatabaseRows
1093
+ x-mortar-trust-surface: app-runtime
1094
+ x-mortar-project-code: allowed
1095
+ summary: Atomically delete rows selected by query filters.
1096
+ description: |
1097
+ Accepts the same filters/order/limit/offset as GET. The returned rows
1098
+ are exactly the rows deleted in the single transaction (maximum 200).
1099
+ Full-text search (`fts`) is rejected on mutation requests.
1100
+ security:
1101
+ - APIKey: []
1102
+ responses:
1103
+ "200":
1104
+ description: Deleted rows.
1105
+ content:
1106
+ application/json:
1107
+ schema:
1108
+ $ref: "#/components/schemas/RowsResponse"
1109
+
1110
+ /v1/{tenant}/db/tables/{table}/rows/{id}:
1111
+ parameters:
1112
+ - $ref: "#/components/parameters/TenantPath"
1113
+ - $ref: "#/components/parameters/TablePath"
1114
+ - name: id
1115
+ in: path
1116
+ required: true
1117
+ schema:
1118
+ type: string
1119
+ get:
1120
+ tags: [data]
1121
+ operationId: getDatabaseRow
1122
+ x-mortar-trust-surface: app-runtime
1123
+ x-mortar-project-code: allowed
1124
+ summary: Fetch one row.
1125
+ security:
1126
+ - APIKey: []
1127
+ responses:
1128
+ "200":
1129
+ description: Row.
1130
+ content:
1131
+ application/json:
1132
+ schema:
1133
+ $ref: "#/components/schemas/Row"
1134
+ "404":
1135
+ description: not_found.
1136
+ patch:
1137
+ tags: [data]
1138
+ operationId: updateDatabaseRow
1139
+ x-mortar-trust-surface: app-runtime
1140
+ x-mortar-project-code: allowed
1141
+ summary: Patch a row's `data` blob.
1142
+ security:
1143
+ - APIKey: []
1144
+ requestBody:
1145
+ required: true
1146
+ content:
1147
+ application/json:
1148
+ schema:
1149
+ $ref: "#/components/schemas/RowBody"
1150
+ responses:
1151
+ "200":
1152
+ description: Updated.
1153
+ content:
1154
+ application/json:
1155
+ schema:
1156
+ $ref: "#/components/schemas/Row"
1157
+ "400":
1158
+ description: invalid_body / table_and_id_required / missing_required_fields.
1159
+ content:
1160
+ application/json:
1161
+ schema:
1162
+ $ref: "#/components/schemas/RowValidationError"
1163
+ "404":
1164
+ description: not_found.
1165
+ delete:
1166
+ tags: [data]
1167
+ operationId: deleteDatabaseRow
1168
+ x-mortar-trust-surface: app-runtime
1169
+ x-mortar-project-code: allowed
1170
+ summary: Delete a row.
1171
+ security:
1172
+ - APIKey: []
1173
+ responses:
1174
+ "204":
1175
+ description: Deleted.
1176
+
1177
+ /v1/{tenant}/storage/files:
1178
+ parameters:
1179
+ - $ref: "#/components/parameters/TenantPath"
1180
+ post:
1181
+ tags: [storage]
1182
+ summary: Upload a single object — body is the raw bytes.
1183
+ description: |
1184
+ `?bucket=` and `?key=` are required. `Content-Type` carries the
1185
+ MIME type (defaults to `application/octet-stream`). Max 50 MB per
1186
+ request. Uploads use immutable physical versions: PostgreSQL atomically
1187
+ switches the logical key only after the new bytes exist, while durable
1188
+ cleanup records reclaim failed uploads and replaced versions.
1189
+ security:
1190
+ - APIKey: []
1191
+ parameters:
1192
+ - name: bucket
1193
+ in: query
1194
+ required: true
1195
+ schema:
1196
+ type: string
1197
+ - name: key
1198
+ in: query
1199
+ required: true
1200
+ schema:
1201
+ type: string
1202
+ requestBody:
1203
+ required: true
1204
+ content:
1205
+ application/octet-stream:
1206
+ schema:
1207
+ type: string
1208
+ format: binary
1209
+ responses:
1210
+ "201":
1211
+ description: Uploaded.
1212
+ content:
1213
+ application/json:
1214
+ schema:
1215
+ $ref: "#/components/schemas/FileObject"
1216
+ examples:
1217
+ avatar:
1218
+ value:
1219
+ bucket: avatars
1220
+ key: users/u_123/main.png
1221
+ size_bytes: 24817
1222
+ content_type: image/png
1223
+ created_at: "2026-05-13T10:42:00Z"
1224
+ "400":
1225
+ description: bucket_and_key_required.
1226
+ "413":
1227
+ description: file_too_large (> 50 MB).
1228
+ get:
1229
+ tags: [storage]
1230
+ summary: List files (optionally filtered by `?bucket=`).
1231
+ security:
1232
+ - APIKey: []
1233
+ parameters:
1234
+ - name: bucket
1235
+ in: query
1236
+ schema:
1237
+ type: string
1238
+ responses:
1239
+ "200":
1240
+ description: Files.
1241
+ content:
1242
+ application/json:
1243
+ schema:
1244
+ type: object
1245
+ properties:
1246
+ files:
1247
+ type: array
1248
+ items:
1249
+ $ref: "#/components/schemas/FileObject"
1250
+ delete:
1251
+ tags: [storage]
1252
+ summary: Delete the object.
1253
+ description: Metadata deletion and a durable physical-cleanup record commit together; backend deletion is asynchronous and retried.
1254
+ security:
1255
+ - APIKey: []
1256
+ parameters:
1257
+ - name: bucket
1258
+ in: query
1259
+ required: true
1260
+ schema:
1261
+ type: string
1262
+ - name: key
1263
+ in: query
1264
+ required: true
1265
+ description: Full object key, may contain slashes.
1266
+ schema:
1267
+ type: string
1268
+ responses:
1269
+ "204":
1270
+ description: Deleted.
1271
+
1272
+ /v1/{tenant}/storage/sign/{bucket}/{key}:
1273
+ parameters:
1274
+ - $ref: "#/components/parameters/TenantPath"
1275
+ - name: bucket
1276
+ in: path
1277
+ required: true
1278
+ schema:
1279
+ type: string
1280
+ - name: key
1281
+ in: path
1282
+ required: true
1283
+ schema:
1284
+ type: string
1285
+ - name: ttl_sec
1286
+ in: query
1287
+ description: TTL in seconds, clamped 60..86400; default 900.
1288
+ schema:
1289
+ type: integer
1290
+ minimum: 60
1291
+ maximum: 86400
1292
+ get:
1293
+ tags: [storage]
1294
+ summary: Mint a short-lived signed URL pointing at the backend.
1295
+ description: >-
1296
+ Authorizes direct object-store access and does not consume the project's
1297
+ API-concurrency budget. Signing is not proof of a download and does not
1298
+ create an object-egress charge; actual object egress is accounted only
1299
+ from provider-observed bytes.
1300
+ security:
1301
+ - APIKey: []
1302
+ responses:
1303
+ "200":
1304
+ description: Signed URL.
1305
+ content:
1306
+ application/json:
1307
+ schema:
1308
+ $ref: "#/components/schemas/SignedURL"
1309
+ "404":
1310
+ description: not_found.
1311
+ "501":
1312
+ description: backend_does_not_sign.
1313
+
1314
+ /v1/{tenant}/realtime/{table}:
1315
+ parameters:
1316
+ - $ref: "#/components/parameters/TenantPath"
1317
+ - $ref: "#/components/parameters/TablePath"
1318
+ get:
1319
+ tags: [realtime]
1320
+ summary: SSE stream of row changes (`insert` / `update` / `delete`).
1321
+ security:
1322
+ - APIKey: []
1323
+ responses:
1324
+ "200":
1325
+ description: text/event-stream of change events.
1326
+ content:
1327
+ text/event-stream:
1328
+ schema:
1329
+ type: string
1330
+ "500":
1331
+ description: stream_not_supported (broker not wired).
1332
+
1333
+ /v1/{tenant}/usage/me:
1334
+ parameters:
1335
+ - $ref: "#/components/parameters/TenantPath"
1336
+ get:
1337
+ tags: [usage]
1338
+ summary: Project's credit balance + compute-spec caps (surfaced by `mortar usage me`).
1339
+ security:
1340
+ - APIKey: []
1341
+ responses:
1342
+ "200":
1343
+ description: Usage summary.
1344
+ content:
1345
+ application/json:
1346
+ schema:
1347
+ $ref: "#/components/schemas/UsageSummary"
1348
+ examples:
1349
+ nano_in_use:
1350
+ value:
1351
+ project_id: proj_01HZ4ABC
1352
+ billing_period: "2026-05"
1353
+ compute_spec: nano
1354
+ tier: nano
1355
+ credit_mode: hard_budget
1356
+ credit:
1357
+ granted_yuan: 99.0
1358
+ used_yuan: 12.34
1359
+ balance_yuan: 86.66
1360
+ used_by_resource:
1361
+ storage: 1.24
1362
+ network_egress: 7.00
1363
+ function: 4.10
1364
+ compute_spec_caps:
1365
+ capacity_units: 1
1366
+ compute_vcpu_slot: 0.05
1367
+ db_ram_gb: 1.0
1368
+ db_disk_gb: 10.0
1369
+ db_max_conns: 60
1370
+ cache_ram_mb: 64.0
1371
+ storage_gb: 10.0
1372
+ network_gb_per_month: 25.0
1373
+ function_cpu_min_per_month: 200
1374
+ log_ingest_gb_per_month: 1
1375
+ baseline_yuan_per_hour: 0.010011
1376
+ monthly_fee_yuan: 0
1377
+ reference_monthly_list_price_yuan: 28
1378
+ pricing_policy_version: 2026-08-cell-100pct-markup-300-nearest8-v4
1379
+ "404":
1380
+ description: not_found.
1381
+
1382
+ /v1/{tenant}/compute/functions/{name}:
1383
+ parameters:
1384
+ - $ref: "#/components/parameters/TenantPath"
1385
+ - $ref: "#/components/parameters/FunctionNamePath"
1386
+ post:
1387
+ tags: [compute]
1388
+ summary: Deploy (or overwrite) a function. Max source 256 KB.
1389
+ security:
1390
+ - APIKey: []
1391
+ requestBody:
1392
+ required: true
1393
+ content:
1394
+ application/json:
1395
+ schema:
1396
+ $ref: "#/components/schemas/FunctionDeployRequest"
1397
+ responses:
1398
+ "201":
1399
+ description: Deployed.
1400
+ content:
1401
+ application/json:
1402
+ schema:
1403
+ $ref: "#/components/schemas/FunctionView"
1404
+ "400":
1405
+ description: name_required / invalid_body / source_required.
1406
+ "413":
1407
+ description: source_too_large.
1408
+ "503":
1409
+ description: compute_not_configured.
1410
+ delete:
1411
+ tags: [compute]
1412
+ summary: Delete the function (idempotent).
1413
+ security:
1414
+ - APIKey: []
1415
+ responses:
1416
+ "204":
1417
+ description: Deleted.
1418
+
1419
+ /v1/{tenant}/compute/functions/{name}/invoke:
1420
+ parameters:
1421
+ - $ref: "#/components/parameters/TenantPath"
1422
+ - $ref: "#/components/parameters/FunctionNamePath"
1423
+ post:
1424
+ tags: [compute]
1425
+ summary: Invoke the function (body is forwarded as the event).
1426
+ security:
1427
+ - APIKey: []
1428
+ requestBody:
1429
+ required: false
1430
+ content:
1431
+ application/json:
1432
+ schema:
1433
+ type: object
1434
+ additionalProperties: true
1435
+ responses:
1436
+ "200":
1437
+ description: Function response (shape function-defined).
1438
+ "503":
1439
+ description: compute_not_configured.
1440
+ get:
1441
+ tags: [compute]
1442
+ summary: Same as POST /invoke but for HTTP-GET-style triggers.
1443
+ security:
1444
+ - APIKey: []
1445
+ responses:
1446
+ "200":
1447
+ description: Function response.
1448
+
1449
+ /v1/{tenant}/compute/functions/{name}/logs:
1450
+ parameters:
1451
+ - $ref: "#/components/parameters/TenantPath"
1452
+ - $ref: "#/components/parameters/FunctionNamePath"
1453
+ get:
1454
+ tags: [compute]
1455
+ summary: Tail recent log entries for the function.
1456
+ parameters:
1457
+ - name: since
1458
+ in: query
1459
+ schema: {type: integer, format: int64}
1460
+ description: Inclusive lower time bound in Unix milliseconds; defaults to ten minutes before until.
1461
+ - name: until
1462
+ in: query
1463
+ schema: {type: integer, format: int64}
1464
+ description: Upper time bound in Unix milliseconds; defaults to now.
1465
+ - name: limit
1466
+ in: query
1467
+ schema: {type: integer, minimum: 1, maximum: 1000, default: 100}
1468
+ - name: request
1469
+ in: query
1470
+ schema: {type: string}
1471
+ description: Provider invocation ID filter.
1472
+ security:
1473
+ - APIKey: []
1474
+ responses:
1475
+ "200":
1476
+ description: Entries.
1477
+ content:
1478
+ application/json:
1479
+ schema:
1480
+ type: object
1481
+ properties:
1482
+ entries:
1483
+ type: array
1484
+ items:
1485
+ $ref: "#/components/schemas/LogEntry"
1486
+ "503":
1487
+ description: Managed-log primitive is not configured.
1488
+ "502":
1489
+ description: log_query_failed.
1490
+
1491
+ /v1/{tenant}/cron/schedules:
1492
+ parameters:
1493
+ - $ref: "#/components/parameters/TenantPath"
1494
+ post:
1495
+ tags: [cron]
1496
+ operationId: createCronSchedule
1497
+ summary: Create (or upsert) a cron schedule for a function.
1498
+ security:
1499
+ - APIKey: []
1500
+ requestBody:
1501
+ required: true
1502
+ content:
1503
+ application/json:
1504
+ schema:
1505
+ $ref: "#/components/schemas/CronScheduleCreateRequest"
1506
+ responses:
1507
+ "201":
1508
+ description: Schedule created or updated.
1509
+ content:
1510
+ application/json:
1511
+ schema:
1512
+ $ref: "#/components/schemas/CronSchedule"
1513
+ "400":
1514
+ description: invalid_body / fields_required / invalid_cron.
1515
+ get:
1516
+ tags: [cron]
1517
+ operationId: listCronSchedules
1518
+ summary: List all schedules in the tenant.
1519
+ security:
1520
+ - APIKey: []
1521
+ responses:
1522
+ "200":
1523
+ description: Schedules.
1524
+ content:
1525
+ application/json:
1526
+ schema:
1527
+ type: object
1528
+ properties:
1529
+ schedules:
1530
+ type: array
1531
+ items:
1532
+ $ref: "#/components/schemas/CronSchedule"
1533
+
1534
+ /v1/{tenant}/cron/schedules/{name}:
1535
+ parameters:
1536
+ - $ref: "#/components/parameters/TenantPath"
1537
+ - name: name
1538
+ in: path
1539
+ required: true
1540
+ schema:
1541
+ type: string
1542
+ patch:
1543
+ tags: [cron]
1544
+ operationId: patchCronSchedule
1545
+ summary: Toggle the enabled flag on a schedule.
1546
+ security:
1547
+ - APIKey: []
1548
+ requestBody:
1549
+ required: true
1550
+ content:
1551
+ application/json:
1552
+ schema:
1553
+ $ref: "#/components/schemas/CronSchedulePatchRequest"
1554
+ responses:
1555
+ "204":
1556
+ description: Updated.
1557
+ "400":
1558
+ description: invalid_body / name_required / enabled_required.
1559
+ "404":
1560
+ description: not_found.
1561
+ delete:
1562
+ tags: [cron]
1563
+ operationId: deleteCronSchedule
1564
+ summary: Delete a schedule (idempotent).
1565
+ security:
1566
+ - APIKey: []
1567
+ responses:
1568
+ "204":
1569
+ description: Deleted.
1570
+ "400":
1571
+ description: name_required.
1572
+
1573
+ /v1/{tenant}/queue/jobs:
1574
+ parameters:
1575
+ - $ref: "#/components/parameters/TenantPath"
1576
+ post:
1577
+ tags: [queue]
1578
+ operationId: enqueueJob
1579
+ summary: Enqueue a background job.
1580
+ security:
1581
+ - APIKey: []
1582
+ requestBody:
1583
+ required: true
1584
+ content:
1585
+ application/json:
1586
+ schema:
1587
+ $ref: "#/components/schemas/QueueEnqueueRequest"
1588
+ responses:
1589
+ "201":
1590
+ description: Created — server fills in id / status / timestamps.
1591
+ content:
1592
+ application/json:
1593
+ schema:
1594
+ $ref: "#/components/schemas/Job"
1595
+ "400":
1596
+ description: invalid_body / type_required / invalid_run_at.
1597
+ "503":
1598
+ description: queue_not_configured.
1599
+ get:
1600
+ tags: [queue]
1601
+ operationId: listJobs
1602
+ summary: List jobs in the tenant (filterable).
1603
+ security:
1604
+ - APIKey: []
1605
+ parameters:
1606
+ - name: type
1607
+ in: query
1608
+ schema:
1609
+ type: string
1610
+ - name: status
1611
+ in: query
1612
+ schema:
1613
+ type: string
1614
+ enum: [pending, running, completed, failed]
1615
+ - name: limit
1616
+ in: query
1617
+ schema:
1618
+ type: integer
1619
+ default: 50
1620
+ - name: offset
1621
+ in: query
1622
+ schema:
1623
+ type: integer
1624
+ default: 0
1625
+ responses:
1626
+ "200":
1627
+ description: Jobs page.
1628
+ content:
1629
+ application/json:
1630
+ schema:
1631
+ type: object
1632
+ properties:
1633
+ jobs:
1634
+ type: array
1635
+ items:
1636
+ $ref: "#/components/schemas/Job"
1637
+ "503":
1638
+ description: queue_not_configured.
1639
+
1640
+ /v1/{tenant}/queue/jobs/{id}:
1641
+ parameters:
1642
+ - $ref: "#/components/parameters/TenantPath"
1643
+ - name: id
1644
+ in: path
1645
+ required: true
1646
+ schema:
1647
+ type: string
1648
+ get:
1649
+ tags: [queue]
1650
+ operationId: getJob
1651
+ summary: Fetch one job by ID.
1652
+ security:
1653
+ - APIKey: []
1654
+ responses:
1655
+ "200":
1656
+ description: Job.
1657
+ content:
1658
+ application/json:
1659
+ schema:
1660
+ $ref: "#/components/schemas/Job"
1661
+ "400":
1662
+ description: id_required.
1663
+ "404":
1664
+ description: not_found.
1665
+ "503":
1666
+ description: queue_not_configured.
1667
+
1668
+ /v1/_accounts/tokens:
1669
+ post:
1670
+ tags: [tokens]
1671
+ operationId: createAccountToken
1672
+ summary: Mint a fixed-lived Personal Access Token (`mtr_pat_*`) for the current account.
1673
+ description: |
1674
+ Raw key is returned exactly ONCE in `raw_key`; subsequent reads only
1675
+ expose the prefix and metadata. Use the raw value as
1676
+ `Authorization: Bearer <raw_key>` against any account-scope endpoint
1677
+ — or as `MORTAR_ACCOUNT_TOKEN` env var for the CLI in CI / headless
1678
+ contexts. Every token has a fixed expiry. `expires_in_days` accepts
1679
+ 1-365; omit / 0 selects the one-year default.
1680
+ security:
1681
+ - AccountToken: []
1682
+ requestBody:
1683
+ required: true
1684
+ content:
1685
+ application/json:
1686
+ schema:
1687
+ $ref: "#/components/schemas/AccountTokenCreateRequest"
1688
+ responses:
1689
+ "201":
1690
+ description: Token created. `raw_key` shown once.
1691
+ content:
1692
+ application/json:
1693
+ schema:
1694
+ $ref: "#/components/schemas/AccountTokenCreated"
1695
+ "400":
1696
+ description: invalid_body / name_required / invalid_token_expiry.
1697
+ get:
1698
+ tags: [tokens]
1699
+ operationId: listAccountTokens
1700
+ summary: List non-revoked PATs for the current account.
1701
+ security:
1702
+ - AccountToken: []
1703
+ responses:
1704
+ "200":
1705
+ description: Tokens.
1706
+ content:
1707
+ application/json:
1708
+ schema:
1709
+ type: object
1710
+ properties:
1711
+ tokens:
1712
+ type: array
1713
+ items:
1714
+ $ref: "#/components/schemas/AccountTokenView"
1715
+
1716
+ /v1/_accounts/tokens/{id}:
1717
+ parameters:
1718
+ - name: id
1719
+ in: path
1720
+ required: true
1721
+ schema:
1722
+ type: string
1723
+ delete:
1724
+ tags: [tokens]
1725
+ operationId: revokeAccountToken
1726
+ summary: Revoke a PAT. Idempotent.
1727
+ security:
1728
+ - AccountToken: []
1729
+ responses:
1730
+ "204":
1731
+ description: Revoked.
1732
+ "404":
1733
+ description: not_found (token doesn't exist or belongs to a different account).
1734
+
1735
+ /v1/_accounts/projects/{id}/domains:
1736
+ parameters:
1737
+ - $ref: "#/components/parameters/ProjectIDPath"
1738
+ post:
1739
+ tags: [domains]
1740
+ operationId: createCustomDomain
1741
+ summary: Claim a custom hostname; returns the TXT record to publish.
1742
+ security:
1743
+ - AccountToken: []
1744
+ requestBody:
1745
+ required: true
1746
+ content:
1747
+ application/json:
1748
+ schema:
1749
+ $ref: "#/components/schemas/CustomDomainCreateRequest"
1750
+ responses:
1751
+ "201":
1752
+ description: Claim created; publish TXT then call /verify.
1753
+ content:
1754
+ application/json:
1755
+ schema:
1756
+ $ref: "#/components/schemas/CustomDomain"
1757
+ "400":
1758
+ description: invalid_body / hostname_required / id_required.
1759
+ "403":
1760
+ description: forbidden.
1761
+ "404":
1762
+ description: not_found.
1763
+ "409":
1764
+ description: hostname_taken.
1765
+ get:
1766
+ tags: [domains]
1767
+ operationId: listCustomDomains
1768
+ summary: List custom domains attached to the project.
1769
+ security:
1770
+ - AccountToken: []
1771
+ responses:
1772
+ "200":
1773
+ description: Domains.
1774
+ content:
1775
+ application/json:
1776
+ schema:
1777
+ type: object
1778
+ properties:
1779
+ domains:
1780
+ type: array
1781
+ items:
1782
+ $ref: "#/components/schemas/CustomDomain"
1783
+ "403":
1784
+ description: forbidden.
1785
+ "404":
1786
+ description: not_found.
1787
+
1788
+ /v1/_accounts/projects/{id}/domains/{hostname}/verify:
1789
+ parameters:
1790
+ - $ref: "#/components/parameters/ProjectIDPath"
1791
+ - name: hostname
1792
+ in: path
1793
+ required: true
1794
+ schema:
1795
+ type: string
1796
+ post:
1797
+ tags: [domains]
1798
+ operationId: verifyCustomDomain
1799
+ summary: Verify ownership via DNS TXT lookup; stamp verified_at.
1800
+ security:
1801
+ - AccountToken: []
1802
+ responses:
1803
+ "200":
1804
+ description: Verified.
1805
+ content:
1806
+ application/json:
1807
+ schema:
1808
+ $ref: "#/components/schemas/CustomDomain"
1809
+ "400":
1810
+ description: hostname_required.
1811
+ "403":
1812
+ description: forbidden.
1813
+ "404":
1814
+ description: not_found.
1815
+ "412":
1816
+ description: txt_not_found (publish TXT then retry).
1817
+ "502":
1818
+ description: dns_lookup_failed.
1819
+
1820
+ /v1/_accounts/projects/{id}/domains/{hostname}:
1821
+ parameters:
1822
+ - $ref: "#/components/parameters/ProjectIDPath"
1823
+ - name: hostname
1824
+ in: path
1825
+ required: true
1826
+ schema:
1827
+ type: string
1828
+ delete:
1829
+ tags: [domains]
1830
+ operationId: deleteCustomDomain
1831
+ summary: Release a custom domain claim.
1832
+ security:
1833
+ - AccountToken: []
1834
+ responses:
1835
+ "204":
1836
+ description: Deleted.
1837
+ "400":
1838
+ description: hostname_required.
1839
+ "403":
1840
+ description: forbidden.
1841
+ "404":
1842
+ description: not_found.
1843
+
1844
+ components:
1845
+ securitySchemes:
1846
+ AccountToken:
1847
+ type: http
1848
+ scheme: bearer
1849
+ bearerFormat: JWT or mtr_pat_*
1850
+ description: |
1851
+ Account-scope auth for every `/v1/_accounts/*` route. Two token
1852
+ kinds are accepted in the same `Authorization: Bearer …` slot:
1853
+ the JWT minted by `/v1/_accounts/signin` (default-TTL session) and
1854
+ a fixed-lived Personal Access Token (`mtr_pat_*`) minted via
1855
+ `POST /v1/_accounts/tokens`. PATs are intended for CI / headless
1856
+ use and may be passed via the `MORTAR_ACCOUNT_TOKEN` env var to
1857
+ the `mortar` CLI.
1858
+ APIKey:
1859
+ type: http
1860
+ scheme: bearer
1861
+ bearerFormat: opaque
1862
+ description: |
1863
+ Project-scope API key starting with `mtr_live_` (production) or
1864
+ `mtr_test_` (sandbox). Bearer-encoded in `Authorization`.
1865
+
1866
+ parameters:
1867
+ TenantPath:
1868
+ name: tenant
1869
+ in: path
1870
+ required: true
1871
+ description: |
1872
+ Tenant ID — the data-isolation boundary. Its value is the
1873
+ project's UUID (the API-key's project; project = the tenant).
1874
+ schema:
1875
+ type: string
1876
+ ProjectIDPath:
1877
+ name: id
1878
+ in: path
1879
+ required: true
1880
+ schema:
1881
+ type: string
1882
+ TablePath:
1883
+ name: table
1884
+ in: path
1885
+ required: true
1886
+ schema:
1887
+ type: string
1888
+ FunctionNamePath:
1889
+ name: name
1890
+ in: path
1891
+ required: true
1892
+ schema:
1893
+ type: string
1894
+
1895
+ responses:
1896
+ BadRequest:
1897
+ description: Malformed request.
1898
+ content:
1899
+ application/json:
1900
+ schema:
1901
+ $ref: "#/components/schemas/Error"
1902
+
1903
+ schemas:
1904
+ Error:
1905
+ type: object
1906
+ required: [error]
1907
+ properties:
1908
+ error:
1909
+ type: string
1910
+ description: Machine-readable error code (e.g. `invalid_body`).
1911
+ detail:
1912
+ type: string
1913
+
1914
+ Account:
1915
+ type: object
1916
+ required: [id, email, subscription_plan, subscription_contract_only, allowed_compute_specs, max_active_projects, created_at]
1917
+ properties:
1918
+ id:
1919
+ type: string
1920
+ email:
1921
+ type: string
1922
+ format: email
1923
+ name:
1924
+ type: string
1925
+ subscription_plan:
1926
+ type: string
1927
+ enum: [free, pro, team, enterprise]
1928
+ subscription_valid_until:
1929
+ type: string
1930
+ format: date-time
1931
+ subscription_monthly_fee_yuan:
1932
+ type: number
1933
+ format: double
1934
+ subscription_compute_grant_yuan:
1935
+ type: number
1936
+ format: double
1937
+ subscription_settlement_kind:
1938
+ type: string
1939
+ enum: [online_payment, aggregate_invoice, contract_invoice]
1940
+ subscription_contract_only:
1941
+ type: boolean
1942
+ description: True for Enterprise; commercial terms are absent until an immutable contract period exists.
1943
+ allowed_compute_specs:
1944
+ type: array
1945
+ items:
1946
+ type: string
1947
+ enum: [nano, micro, small, medium, large, xl, 2xl, 4xl, 8xl, 16xl]
1948
+ max_active_projects:
1949
+ type: integer
1950
+ minimum: 0
1951
+ description: Account-level active-project ceiling; 0 means no catalog-level count ceiling.
1952
+ notification_email:
1953
+ type: string
1954
+ format: email
1955
+ description: Present only after proof of control.
1956
+ notification_email_verified_at:
1957
+ type: string
1958
+ format: date-time
1959
+ created_at:
1960
+ type: string
1961
+ format: date-time
1962
+
1963
+ AccountSignupRequest:
1964
+ type: object
1965
+ required: [email, password]
1966
+ properties:
1967
+ email:
1968
+ type: string
1969
+ format: email
1970
+ password:
1971
+ type: string
1972
+ minLength: 8
1973
+ name:
1974
+ type: string
1975
+
1976
+ AccountSigninRequest:
1977
+ type: object
1978
+ required: [email, password]
1979
+ properties:
1980
+ email:
1981
+ type: string
1982
+ password:
1983
+ type: string
1984
+
1985
+ AccountTokenResponse:
1986
+ type: object
1987
+ required: [token, expires_at, account]
1988
+ properties:
1989
+ token:
1990
+ type: string
1991
+ expires_at:
1992
+ type: string
1993
+ format: date-time
1994
+ account:
1995
+ $ref: "#/components/schemas/Account"
1996
+
1997
+ Project:
1998
+ type: object
1999
+ description: "A project has exactly one customer resource-spec axis: compute_spec for retail compute, or capacity_profile for an Enterprise capacity contract."
2000
+ required: [id, name, credit_mode, created_at]
2001
+ properties:
2002
+ id:
2003
+ type: string
2004
+ name:
2005
+ type: string
2006
+ compute_spec:
2007
+ type: string
2008
+ enum: [nano, micro, small, medium, large, xl, 2xl, 4xl, 8xl, 16xl]
2009
+ description: Retail compute specification; omitted when capacity_profile is present.
2010
+ tier:
2011
+ type: string
2012
+ enum: [nano, micro, small, medium, large, xl, 2xl, 4xl, 8xl, 16xl]
2013
+ deprecated: true
2014
+ description: Compatibility alias for compute_spec; omitted on Enterprise capacity projects.
2015
+ credit_mode:
2016
+ type: string
2017
+ enum: [hard_budget, post_pay]
2018
+ capacity_profile:
2019
+ type: string
2020
+ description: Logical profile on an Enterprise capacity project; mutually exclusive with compute_spec and tier.
2021
+ created_at:
2022
+ type: string
2023
+ format: date-time
2024
+
2025
+ ProjectCreateRequest:
2026
+ type: object
2027
+ required: [name]
2028
+ properties:
2029
+ name:
2030
+ type: string
2031
+ compute_spec:
2032
+ type: string
2033
+ enum: [nano, micro, small, medium, large, xl, 2xl, 4xl, 8xl, 16xl]
2034
+ tier:
2035
+ type: string
2036
+ enum: [nano, micro, small, medium, large, xl, 2xl, 4xl, 8xl, 16xl]
2037
+ deprecated: true
2038
+ credit_mode:
2039
+ type: string
2040
+ capacity_profile:
2041
+ type: string
2042
+ description: Enterprise only; mutually exclusive with compute_spec and tier.
2043
+
2044
+ ProjectUpdateRequest:
2045
+ type: object
2046
+ properties:
2047
+ name:
2048
+ type: string
2049
+ compute_spec:
2050
+ type: string
2051
+ enum: [nano, micro, small, medium, large, xl, 2xl, 4xl, 8xl, 16xl]
2052
+ tier:
2053
+ type: string
2054
+ enum: [nano, micro, small, medium, large, xl, 2xl, 4xl, 8xl, 16xl]
2055
+ deprecated: true
2056
+ credit_mode:
2057
+ type: string
2058
+ capacity_profile:
2059
+ type: string
2060
+ description: Enterprise only; changes the owned project's logical profile through Mortar admission. Mutually exclusive with compute_spec and tier; a placed capacity project cannot be changed through the retail compute_spec axis.
2061
+
2062
+ EnterpriseContract:
2063
+ type: object
2064
+ required: [generated_at, profiles, profile_rates, usage_rates, admission_open]
2065
+ properties:
2066
+ generated_at:
2067
+ type: string
2068
+ format: date-time
2069
+ profiles:
2070
+ type: array
2071
+ items:
2072
+ $ref: "#/components/schemas/EnterpriseCapacityProfile"
2073
+ profile_rate_version:
2074
+ type: string
2075
+ profile_rates:
2076
+ type: array
2077
+ items:
2078
+ $ref: "#/components/schemas/EnterpriseProfileRate"
2079
+ usage_rate_version:
2080
+ type: string
2081
+ usage_rates:
2082
+ type: array
2083
+ items:
2084
+ $ref: "#/components/schemas/EnterpriseUsageRate"
2085
+ admission_open:
2086
+ type: boolean
2087
+
2088
+ EnterpriseCapacityProfile:
2089
+ type: object
2090
+ required: [id, display_name, admission_units, max_api_concurrency, max_db_query_concurrency, max_realtime_connections, max_function_concurrency, hot_tenant_eligibility, security_isolation]
2091
+ properties:
2092
+ id: {type: string}
2093
+ display_name: {type: string}
2094
+ admission_units: {type: integer, format: int64}
2095
+ max_api_concurrency: {type: integer}
2096
+ max_db_query_concurrency: {type: integer}
2097
+ max_realtime_connections: {type: integer}
2098
+ max_function_concurrency: {type: integer}
2099
+ hot_tenant_eligibility: {type: string}
2100
+ security_isolation: {type: string}
2101
+
2102
+ EnterpriseProfileRate:
2103
+ type: object
2104
+ required: [profile_id, rate_version, hourly_rate_micros]
2105
+ properties:
2106
+ profile_id: {type: string}
2107
+ rate_version: {type: string}
2108
+ hourly_rate_micros: {type: integer, format: int64}
2109
+
2110
+ EnterpriseUsageRate:
2111
+ type: object
2112
+ required: [resource, native_unit, billing_unit, native_units_per_billing_unit, rate_micros_per_billing_unit]
2113
+ properties:
2114
+ resource: {type: string}
2115
+ native_unit: {type: string}
2116
+ billing_unit: {type: string}
2117
+ native_units_per_billing_unit: {type: number, format: double}
2118
+ rate_micros_per_billing_unit: {type: integer, format: int64}
2119
+
2120
+ ProjectWriteSuspensionRequest:
2121
+ type: object
2122
+ required: [suspended]
2123
+ properties:
2124
+ suspended:
2125
+ type: boolean
2126
+ reason:
2127
+ type: string
2128
+ minLength: 3
2129
+ maxLength: 200
2130
+ description: Required when suspended is true; ignored when resuming.
2131
+
2132
+ ProjectWriteSuspension:
2133
+ type: object
2134
+ required: [project_id, write_suspended, reason]
2135
+ properties:
2136
+ project_id:
2137
+ type: string
2138
+ write_suspended:
2139
+ type: boolean
2140
+ reason:
2141
+ type: string
2142
+ suspended_at:
2143
+ type: string
2144
+ format: date-time
2145
+
2146
+ TierOverflow:
2147
+ type: object
2148
+ properties:
2149
+ error:
2150
+ type: string
2151
+ example: tier_change_would_overflow
2152
+ target:
2153
+ type: string
2154
+ blockers:
2155
+ type: array
2156
+ items:
2157
+ type: object
2158
+ properties:
2159
+ resource:
2160
+ type: string
2161
+ current:
2162
+ type: integer
2163
+ format: int64
2164
+ new_limit:
2165
+ type: integer
2166
+ format: int64
2167
+
2168
+ TopupRequest:
2169
+ type: object
2170
+ required: [provider, amount_yuan]
2171
+ properties:
2172
+ provider:
2173
+ type: string
2174
+ enum: [wechat_pay, alipay]
2175
+ amount_yuan:
2176
+ type: number
2177
+ format: double
2178
+ minimum: 1.0
2179
+ maximum: 10000.0
2180
+ method:
2181
+ type: string
2182
+ description: Optional alipay sub-method (`scan` / `page`).
2183
+
2184
+ ChargeResult:
2185
+ type: object
2186
+ required: [provider, external_ref]
2187
+ properties:
2188
+ provider:
2189
+ type: string
2190
+ external_ref:
2191
+ type: string
2192
+ qr_code_data:
2193
+ type: string
2194
+ redirect_url:
2195
+ type: string
2196
+ expires_at_unix_sec:
2197
+ type: integer
2198
+ format: int64
2199
+
2200
+ WebhookEvent:
2201
+ type: object
2202
+ description: |
2203
+ Provider-shaped payload — the body is opaque to Mortar; the
2204
+ provider's `VerifyWebhook` parses + normalises it.
2205
+ additionalProperties: true
2206
+
2207
+ APIKeyCreateRequest:
2208
+ type: object
2209
+ required: [name]
2210
+ properties:
2211
+ name:
2212
+ type: string
2213
+ scope:
2214
+ type: string
2215
+ enum: [admin, app, public]
2216
+ default: app
2217
+ env:
2218
+ type: string
2219
+ enum: [live, test]
2220
+ default: live
2221
+
2222
+ APIKey:
2223
+ type: object
2224
+ required: [id, name, prefix, scope, raw_key, created_at]
2225
+ properties:
2226
+ id:
2227
+ type: string
2228
+ name:
2229
+ type: string
2230
+ prefix:
2231
+ type: string
2232
+ example: mtr_live_abcd
2233
+ scope:
2234
+ type: string
2235
+ enum: [admin, app, public]
2236
+ raw_key:
2237
+ type: string
2238
+ description: Full key, returned ONCE at creation time.
2239
+ created_at:
2240
+ type: string
2241
+ format: date-time
2242
+
2243
+ UserAuthRequest:
2244
+ type: object
2245
+ required: [email, password]
2246
+ properties:
2247
+ email:
2248
+ type: string
2249
+ format: email
2250
+ password:
2251
+ type: string
2252
+ minLength: 8
2253
+
2254
+ UserAuthResponse:
2255
+ type: object
2256
+ required: [access_token, user_id, expires_in_sec]
2257
+ properties:
2258
+ access_token:
2259
+ type: string
2260
+ refresh_token:
2261
+ type: string
2262
+ user_id:
2263
+ type: string
2264
+ email:
2265
+ type: string
2266
+ phone:
2267
+ type: string
2268
+ data_role:
2269
+ type: string
2270
+ enum: [user, admin]
2271
+ expires_in_sec:
2272
+ type: integer
2273
+ refresh_expires_in_sec:
2274
+ type: integer
2275
+
2276
+ TableColumn:
2277
+ type: object
2278
+ required: [name, type]
2279
+ properties:
2280
+ name:
2281
+ type: string
2282
+ type:
2283
+ type: string
2284
+ enum: [text, number, bool, json, timestamp, uuid]
2285
+ required:
2286
+ type: boolean
2287
+
2288
+ MigrationPlanRequest:
2289
+ type: object
2290
+ required: [migration_id, sql]
2291
+ properties:
2292
+ migration_id:
2293
+ type: string
2294
+ pattern: '^[0-9]{4,14}_[a-z0-9][a-z0-9_]*\.sql$'
2295
+ example: 0007_create_orders.sql
2296
+ sql:
2297
+ type: string
2298
+ description: Exact Mortar logical SQL bytes represented as a JSON string.
2299
+
2300
+ MigrationApplyRequest:
2301
+ allOf:
2302
+ - $ref: "#/components/schemas/MigrationPlanRequest"
2303
+ - type: object
2304
+ required: [checksum, expected_revision]
2305
+ properties:
2306
+ checksum:
2307
+ type: string
2308
+ pattern: '^sha256:[a-f0-9]{64}$'
2309
+ expected_revision:
2310
+ type: integer
2311
+ format: int64
2312
+ minimum: 0
2313
+
2314
+ MigrationOperation:
2315
+ type: object
2316
+ required: [kind, table]
2317
+ properties:
2318
+ kind:
2319
+ type: string
2320
+ enum: [create_table, drop_table, set_table_access, add_column, drop_column, create_index]
2321
+ table:
2322
+ type: string
2323
+ access:
2324
+ type: string
2325
+ enum: [private, public_read, public, public_insert, app_only]
2326
+ columns:
2327
+ type: array
2328
+ items:
2329
+ $ref: "#/components/schemas/TableColumn"
2330
+ column:
2331
+ type: string
2332
+ type:
2333
+ type: string
2334
+ enum: [text, number, bool, json, timestamp, uuid]
2335
+ required:
2336
+ type: boolean
2337
+ fields:
2338
+ type: array
2339
+ items:
2340
+ type: string
2341
+
2342
+ MigrationDiagnostic:
2343
+ type: object
2344
+ required: [severity, code, message]
2345
+ properties:
2346
+ severity:
2347
+ type: string
2348
+ enum: [warning, error]
2349
+ code:
2350
+ type: string
2351
+ message:
2352
+ type: string
2353
+ statement:
2354
+ type: integer
2355
+
2356
+ MigrationPlan:
2357
+ type: object
2358
+ required: [version, migration_id, checksum, current_revision, operations, diagnostics, destructive, executable]
2359
+ properties:
2360
+ version:
2361
+ type: string
2362
+ enum: [mortar.migration-plan.v1]
2363
+ migration_id:
2364
+ type: string
2365
+ checksum:
2366
+ type: string
2367
+ current_revision:
2368
+ type: integer
2369
+ format: int64
2370
+ operations:
2371
+ type: array
2372
+ items:
2373
+ $ref: "#/components/schemas/MigrationOperation"
2374
+ diagnostics:
2375
+ type: array
2376
+ items:
2377
+ $ref: "#/components/schemas/MigrationDiagnostic"
2378
+ destructive:
2379
+ type: boolean
2380
+ executable:
2381
+ type: boolean
2382
+ already_applied:
2383
+ type: boolean
2384
+
2385
+ MigrationApplyResponse:
2386
+ type: object
2387
+ required: [plan, revision, applied]
2388
+ properties:
2389
+ plan:
2390
+ $ref: "#/components/schemas/MigrationPlan"
2391
+ revision:
2392
+ type: integer
2393
+ format: int64
2394
+ applied:
2395
+ type: boolean
2396
+
2397
+ TableCreateRequest:
2398
+ type: object
2399
+ required: [name]
2400
+ properties:
2401
+ name:
2402
+ type: string
2403
+ columns:
2404
+ type: array
2405
+ items:
2406
+ type: object
2407
+ required: [name]
2408
+ properties:
2409
+ name:
2410
+ type: string
2411
+ type:
2412
+ type: string
2413
+ required:
2414
+ type: boolean
2415
+
2416
+ Table:
2417
+ type: object
2418
+ properties:
2419
+ name:
2420
+ type: string
2421
+ created_at:
2422
+ type: string
2423
+ format: date-time
2424
+ columns:
2425
+ type: array
2426
+ items:
2427
+ type: object
2428
+ properties:
2429
+ name:
2430
+ type: string
2431
+ type:
2432
+ type: string
2433
+ required:
2434
+ type: boolean
2435
+
2436
+ RowBody:
2437
+ type: object
2438
+ description: >
2439
+ Row insert / update envelope. The row's actual fields go under
2440
+ the `data` key — sending a flat body (e.g. `{"email": "..."}`
2441
+ directly) writes an empty row, because the server only reads
2442
+ the `data` property. `id`, `created_at`, and `updated_at` are
2443
+ server-owned metadata rather than application payload fields.
2444
+ required: [data]
2445
+ properties:
2446
+ data:
2447
+ type: object
2448
+ description: >
2449
+ The row's JSONB payload. Keys declared as `required: true`
2450
+ on the table (see TableCreateRequest) must be present and
2451
+ non-null, otherwise the server returns 400
2452
+ missing_required_fields. Logical type names inform AI-authored
2453
+ application types but are not runtime value validators. Do not
2454
+ send the server-owned `id`, `created_at`, or `updated_at` keys in
2455
+ ordinary insert/update payloads; documented upsert may use `id`
2456
+ only as row identity.
2457
+ additionalProperties: true
2458
+
2459
+ RowBatchBody:
2460
+ type: object
2461
+ required: [rows]
2462
+ properties:
2463
+ rows:
2464
+ type: array
2465
+ minItems: 1
2466
+ maxItems: 200
2467
+ items:
2468
+ type: object
2469
+ additionalProperties: true
2470
+ description: >
2471
+ All rows are inserted/upserted in one PostgreSQL transaction. Batch
2472
+ atomicity does not make retrying this POST idempotent. Each item is an
2473
+ application payload; omit server-owned `id`, `created_at`, and
2474
+ `updated_at` except that upsert may use `id` through its documented SDK
2475
+ contract.
2476
+
2477
+ Row:
2478
+ type: object
2479
+ description: >
2480
+ Stored row envelope. The SDK flattens the server-owned `id`,
2481
+ `created_at`, and `updated_at` metadata beside the application payload
2482
+ when it returns a row; those names are not application-owned fields.
2483
+ properties:
2484
+ id:
2485
+ type: string
2486
+ table:
2487
+ type: string
2488
+ data:
2489
+ type: object
2490
+ additionalProperties: true
2491
+ created_at:
2492
+ type: string
2493
+ format: date-time
2494
+ updated_at:
2495
+ type: string
2496
+ format: date-time
2497
+
2498
+ RowsResponse:
2499
+ type: object
2500
+ required: [rows]
2501
+ properties:
2502
+ rows:
2503
+ type: array
2504
+ items:
2505
+ $ref: "#/components/schemas/Row"
2506
+
2507
+ DBTransactionRequest:
2508
+ type: object
2509
+ required: [operations]
2510
+ properties:
2511
+ operations:
2512
+ type: array
2513
+ minItems: 1
2514
+ maxItems: 200
2515
+ items:
2516
+ $ref: "#/components/schemas/DBTransactionOperation"
2517
+
2518
+ DBTransactionOperation:
2519
+ type: object
2520
+ required: [op]
2521
+ description: |
2522
+ `insert`: table + data. `update`: table + UUID id + complete replacement
2523
+ data. `delete`: table + UUID id. `enqueue`: type plus optional payload,
2524
+ run_at, and max_attempts.
2525
+ properties:
2526
+ op:
2527
+ type: string
2528
+ enum: [insert, update, delete, enqueue]
2529
+ table:
2530
+ type: string
2531
+ id:
2532
+ type: string
2533
+ format: uuid
2534
+ data:
2535
+ type: object
2536
+ additionalProperties: true
2537
+ type:
2538
+ type: string
2539
+ payload: {}
2540
+ run_at:
2541
+ type: string
2542
+ format: date-time
2543
+ max_attempts:
2544
+ type: integer
2545
+
2546
+ DBTransactionResponse:
2547
+ type: object
2548
+ required: [results]
2549
+ properties:
2550
+ results:
2551
+ type: array
2552
+ items:
2553
+ type: object
2554
+ required: [op]
2555
+ properties:
2556
+ op:
2557
+ type: string
2558
+ row:
2559
+ $ref: "#/components/schemas/Row"
2560
+ job:
2561
+ $ref: "#/components/schemas/Job"
2562
+
2563
+ RowValidationError:
2564
+ type: object
2565
+ description: >
2566
+ Returned on 400 when the row body fails validation. `error` is
2567
+ the machine-readable code; `fields` (only for
2568
+ `missing_required_fields`) lists the column names that were
2569
+ absent / null.
2570
+ required: [error]
2571
+ properties:
2572
+ error:
2573
+ type: string
2574
+ enum:
2575
+ - invalid_body
2576
+ - table_required
2577
+ - table_and_id_required
2578
+ - missing_required_fields
2579
+ fields:
2580
+ type: array
2581
+ items:
2582
+ type: string
2583
+
2584
+ FileObject:
2585
+ type: object
2586
+ required: [bucket, key, size_bytes, content_type, created_at]
2587
+ properties:
2588
+ bucket:
2589
+ type: string
2590
+ key:
2591
+ type: string
2592
+ size_bytes:
2593
+ type: integer
2594
+ format: int64
2595
+ content_type:
2596
+ type: string
2597
+ created_at:
2598
+ type: string
2599
+ format: date-time
2600
+
2601
+ SignedURL:
2602
+ type: object
2603
+ properties:
2604
+ url:
2605
+ type: string
2606
+ expires_in_sec:
2607
+ type: integer
2608
+ format: int64
2609
+ size_bytes:
2610
+ type: integer
2611
+ format: int64
2612
+ description: Object metadata only; it is not a billed egress projection.
2613
+
2614
+ UsageSummary:
2615
+ type: object
2616
+ properties:
2617
+ project_id:
2618
+ type: string
2619
+ billing_period:
2620
+ type: string
2621
+ example: "2026-05"
2622
+ compute_spec:
2623
+ type: string
2624
+ tier:
2625
+ type: string
2626
+ deprecated: true
2627
+ credit_mode:
2628
+ type: string
2629
+ credit:
2630
+ type: object
2631
+ properties:
2632
+ granted_yuan:
2633
+ type: number
2634
+ used_yuan:
2635
+ type: number
2636
+ balance_yuan:
2637
+ type: number
2638
+ used_by_resource:
2639
+ type: object
2640
+ additionalProperties:
2641
+ type: number
2642
+ compute_spec_caps:
2643
+ type: object
2644
+ properties:
2645
+ capacity_units:
2646
+ type: integer
2647
+ format: int64
2648
+ compute_vcpu_slot:
2649
+ type: number
2650
+ description: vCPU share allocated to this tenant from the pool ECS.
2651
+ db_ram_gb:
2652
+ type: number
2653
+ db_disk_gb:
2654
+ type: number
2655
+ db_max_conns:
2656
+ type: integer
2657
+ cache_ram_mb:
2658
+ type: number
2659
+ storage_gb:
2660
+ type: number
2661
+ network_gb_per_month:
2662
+ type: number
2663
+ function_cpu_min_per_month:
2664
+ type: integer
2665
+ format: int64
2666
+ log_ingest_gb_per_month:
2667
+ type: number
2668
+ description: Hard cap for original uncompressed managed-log ingestion per month.
2669
+ baseline_yuan_per_hour:
2670
+ type: number
2671
+ description: Raw 100%-allocated Cell COGS before retail markup.
2672
+ monthly_fee_yuan:
2673
+ type: number
2674
+ description: Actual fixed fee charged for the current spec; Nano is zero-rated by Free.
2675
+ reference_monthly_list_price_yuan:
2676
+ type: number
2677
+ description: Cost-calibrated reference price before a plan inclusion such as Free Nano.
2678
+ pricing_policy_version:
2679
+ type: string
2680
+ tier_caps:
2681
+ deprecated: true
2682
+ description: Compatibility alias for compute_spec_caps.
2683
+ allOf:
2684
+ - $ref: "#/components/schemas/ComputeSpecCaps"
2685
+
2686
+ ComputeSpecCaps:
2687
+ type: object
2688
+ properties:
2689
+ capacity_units:
2690
+ type: integer
2691
+ format: int64
2692
+ compute_vcpu_slot:
2693
+ type: number
2694
+ db_ram_gb:
2695
+ type: number
2696
+ db_disk_gb:
2697
+ type: number
2698
+ db_max_conns:
2699
+ type: integer
2700
+ cache_ram_mb:
2701
+ type: number
2702
+ storage_gb:
2703
+ type: number
2704
+ network_gb_per_month:
2705
+ type: number
2706
+ function_cpu_min_per_month:
2707
+ type: integer
2708
+ format: int64
2709
+ log_ingest_gb_per_month:
2710
+ type: number
2711
+ description: Hard cap for original uncompressed managed-log ingestion per month.
2712
+ baseline_yuan_per_hour:
2713
+ type: number
2714
+ description: Raw 100%-allocated Cell COGS before retail markup.
2715
+ monthly_fee_yuan:
2716
+ type: number
2717
+ reference_monthly_list_price_yuan:
2718
+ type: number
2719
+ pricing_policy_version:
2720
+ type: string
2721
+
2722
+ FunctionDeployRequest:
2723
+ type: object
2724
+ required: [source]
2725
+ properties:
2726
+ runtime:
2727
+ type: string
2728
+ default: node
2729
+ source:
2730
+ type: string
2731
+ description: User code, max 256 KB.
2732
+
2733
+ FunctionView:
2734
+ type: object
2735
+ properties:
2736
+ name:
2737
+ type: string
2738
+ runtime:
2739
+ type: string
2740
+
2741
+ LogEntry:
2742
+ type: object
2743
+ required: [timestamp_ns, message]
2744
+ properties:
2745
+ timestamp_ns:
2746
+ type: integer
2747
+ format: int64
2748
+ description: Provider-canonical Unix timestamp in nanoseconds.
2749
+ message:
2750
+ type: string
2751
+ stream:
2752
+ type: string
2753
+ enum: [stdout, stderr, system]
2754
+ request_id:
2755
+ type: string
2756
+ description: Provider invocation ID when one is available.
2757
+
2758
+ Bundle:
2759
+ type: object
2760
+ description: |
2761
+ Reserved for future use — Mortar's compute primitive will expose
2762
+ deployed-bundle metadata here when the function-deploy path
2763
+ moves from inline source to a content-addressed bundle blob.
2764
+ properties:
2765
+ id:
2766
+ type: string
2767
+ sha256:
2768
+ type: string
2769
+ size_bytes:
2770
+ type: integer
2771
+ format: int64
2772
+ created_at:
2773
+ type: string
2774
+ format: date-time
2775
+
2776
+ CronScheduleCreateRequest:
2777
+ type: object
2778
+ required: [name, function_name, cron]
2779
+ properties:
2780
+ name:
2781
+ type: string
2782
+ description: Stable schedule identifier (unique within tenant).
2783
+ function_name:
2784
+ type: string
2785
+ description: Target compute function to invoke.
2786
+ cron:
2787
+ type: string
2788
+ description: Standard 5-field cron expression (UTC).
2789
+ example: "*/15 * * * *"
2790
+ enabled:
2791
+ type: boolean
2792
+ default: true
2793
+
2794
+ CronSchedulePatchRequest:
2795
+ type: object
2796
+ required: [enabled]
2797
+ properties:
2798
+ enabled:
2799
+ type: boolean
2800
+
2801
+ CronSchedule:
2802
+ type: object
2803
+ required: [name, function_name, cron, enabled, next_run_unix, last_run_unix]
2804
+ properties:
2805
+ name:
2806
+ type: string
2807
+ function_name:
2808
+ type: string
2809
+ cron:
2810
+ type: string
2811
+ enabled:
2812
+ type: boolean
2813
+ next_run_unix:
2814
+ type: integer
2815
+ format: int64
2816
+ last_run_unix:
2817
+ type: integer
2818
+ format: int64
2819
+
2820
+ QueueEnqueueRequest:
2821
+ type: object
2822
+ required: [type]
2823
+ properties:
2824
+ type:
2825
+ type: string
2826
+ description: Job-type identifier matched by the registered worker.
2827
+ payload:
2828
+ description: Opaque JSON forwarded to the handler.
2829
+ run_at:
2830
+ type: string
2831
+ format: date-time
2832
+ description: RFC3339 timestamp; omit/empty for "now".
2833
+ max_attempts:
2834
+ type: integer
2835
+ default: 3
2836
+
2837
+ Job:
2838
+ type: object
2839
+ required:
2840
+ [id, type, status, run_at, max_attempts, attempt_count, created_at, updated_at]
2841
+ properties:
2842
+ id:
2843
+ type: string
2844
+ type:
2845
+ type: string
2846
+ payload:
2847
+ description: Opaque JSON.
2848
+ status:
2849
+ type: string
2850
+ enum: [pending, running, completed, failed]
2851
+ run_at:
2852
+ type: string
2853
+ format: date-time
2854
+ max_attempts:
2855
+ type: integer
2856
+ attempt_count:
2857
+ type: integer
2858
+ last_error:
2859
+ type: string
2860
+ created_at:
2861
+ type: string
2862
+ format: date-time
2863
+ updated_at:
2864
+ type: string
2865
+ format: date-time
2866
+
2867
+ AccountTokenCreateRequest:
2868
+ type: object
2869
+ required: [name]
2870
+ properties:
2871
+ name:
2872
+ type: string
2873
+ description: Human-readable label (e.g. "ci-deploy").
2874
+ expires_in_days:
2875
+ type: integer
2876
+ minimum: 0
2877
+ maximum: 365
2878
+ description: Days until expiry; omit / 0 selects the one-year default. Values 1-365 choose a shorter fixed lifetime.
2879
+
2880
+ AccountTokenView:
2881
+ type: object
2882
+ required: [id, name, prefix, created_at, expires_at]
2883
+ properties:
2884
+ id:
2885
+ type: string
2886
+ name:
2887
+ type: string
2888
+ prefix:
2889
+ type: string
2890
+ description: First 12 chars (e.g. `mtr_pat_a1bc`) — indexed lookup handle, safe to log.
2891
+ created_at:
2892
+ type: string
2893
+ format: date-time
2894
+ last_used_at:
2895
+ type: string
2896
+ format: date-time
2897
+ expires_at:
2898
+ type: string
2899
+ format: date-time
2900
+
2901
+ AccountTokenCreated:
2902
+ allOf:
2903
+ - $ref: "#/components/schemas/AccountTokenView"
2904
+ - type: object
2905
+ required: [raw_key]
2906
+ properties:
2907
+ raw_key:
2908
+ type: string
2909
+ description: The unhashed PAT. Surfaced ONCE at creation; the server keeps only HMAC(raw_key).
2910
+
2911
+ CustomDomainCreateRequest:
2912
+ type: object
2913
+ required: [hostname]
2914
+ properties:
2915
+ hostname:
2916
+ type: string
2917
+
2918
+ CustomDomain:
2919
+ type: object
2920
+ required: [hostname, project_id, verified, cert_status, created_at]
2921
+ properties:
2922
+ hostname:
2923
+ type: string
2924
+ project_id:
2925
+ type: string
2926
+ verified:
2927
+ type: boolean
2928
+ verified_at:
2929
+ type: string
2930
+ format: date-time
2931
+ cert_status:
2932
+ type: string
2933
+ description: Lifecycle of the issued TLS cert (e.g. pending, issued).
2934
+ txt_record:
2935
+ type: string
2936
+ description: Hostname to publish a TXT record under (e.g. `_mortar.example.com`).
2937
+ txt_value:
2938
+ type: string
2939
+ description: The opaque verification token to set as the TXT value.
2940
+ created_at:
2941
+ type: string
2942
+ format: date-time