zuka
zuka/openapi.json

worklyn / zukapublic

Agent-first git hosting. One Rust binary: git over HTTP and SSH, a REST API, MCP, CI, and multi-tenant isolation.

Get a copy: git clone https://zuka.worklyn.com/worklyn/zuka.git
zuka/openapi.json
JSONopenapi.json63.3 KBDownload
1{
2 "openapi": "3.1.0",
3 "info": {
4 "title": "zuka",
5 "version": "0.1.0",
6 "summary": "Git hosting whose primary client is an agent.",
7 "description": "Bare git repositories with a REST API, the git smart-HTTP wire protocol, and SSH.\n\nAuthentication is a bearer token on `/v1/*`, and HTTP Basic on the git paths where the username is ignored and the password is the token. SSH authenticates with a registered public key.\n\nErrors on `/v1/*` are RFC 9457 `application/problem+json` with a stable `type` URI; clients discriminate on `type` and read extension members, never on `detail`. The git paths under `/{account}/{repo}.git/` deliberately answer in `text/plain` instead, because git prints the response body straight at the user.\n\nAccount and repository names are case-folded: `Alice/Site` and `alice/site` are the same resource.\n\nSSH is also served. Authentication is a registered public key; the SSH username is ignored and the account is derived from which key matched. A session may only exec `git-upload-pack` or `git-receive-pack` \u2014 there is no shell, no pty and no subsystem. Register keys with `POST /v1/keys`.\n\nSSH serves `git-upload-pack`, `git-receive-pack` and `git-upload-archive`, so `git archive --remote` works alongside clone, fetch and push.\n\nStorage quotas and per-credential rate limits apply to `/v1` and `/mcp`. Over-quota blocks writes only \u2014 reads and clones keep working. The git transport is not rate limited; it is bounded by a concurrency semaphore instead.\n\nList endpoints return `{items, truncated}`. There is no cursor: a previous draft emitted `next_cursor: null` on lists that had been cut short, which told clients the list had ended when it had not.",
8 "license": {
9 "name": "Apache-2.0"
10 }
11 },
12 "servers": [
13 {
14 "url": "http://127.0.0.1:8790",
15 "description": "Local standalone"
16 }
17 ],
18 "tags": [
19 {
20 "name": "service",
21 "description": "Health and discovery."
22 },
23 {
24 "name": "account",
25 "description": "The authenticated identity."
26 },
27 {
28 "name": "repositories",
29 "description": "Repository lifecycle and refs."
30 },
31 {
32 "name": "keys",
33 "description": "SSH public keys."
34 },
35 {
36 "name": "git",
37 "description": "The git wire protocol. Not REST."
38 },
39 {
40 "name": "mcp",
41 "description": "Model Context Protocol surface for agents."
42 },
43 {
44 "name": "ci",
45 "description": "Continuous integration runs."
46 }
47 ],
48 "security": [
49 {
50 "bearerAuth": []
51 }
52 ],
53 "paths": {
54 "/healthz": {
55 "get": {
56 "tags": [
57 "service"
58 ],
59 "operationId": "health",
60 "summary": "Liveness and disk headroom",
61 "description": "Returns 503 when free space is below the reserve, because writes are refused in that state.",
62 "responses": {
63 "200": {
64 "description": "Healthy.",
65 "content": {
66 "application/json": {
67 "schema": {
68 "$ref": "#/components/schemas/Health"
69 }
70 }
71 }
72 },
73 "503": {
74 "description": "Below the disk reserve; writes are refused."
75 }
76 }
77 }
78 },
79 "/openapi.json": {
80 "get": {
81 "tags": [
82 "service"
83 ],
84 "operationId": "openapi",
85 "summary": "This document",
86 "responses": {
87 "200": {
88 "description": "The OpenAPI document."
89 }
90 }
91 }
92 },
93 "/v1/account": {
94 "get": {
95 "tags": [
96 "account"
97 ],
98 "operationId": "getAccount",
99 "summary": "The authenticated identity",
100 "security": [
101 {
102 "bearerAuth": []
103 }
104 ],
105 "responses": {
106 "200": {
107 "description": "The account, its scopes, and any repository confinement.",
108 "content": {
109 "application/json": {
110 "schema": {
111 "$ref": "#/components/schemas/Account"
112 }
113 }
114 }
115 },
116 "401": {
117 "$ref": "#/components/responses/Unauthorized"
118 },
119 "429": {
120 "$ref": "#/components/responses/RateLimited"
121 }
122 }
123 }
124 },
125 "/v1/keys": {
126 "get": {
127 "tags": [
128 "keys"
129 ],
130 "operationId": "listKeys",
131 "summary": "List SSH public keys",
132 "description": "Requires an unconfined admin token: a repo-scoped token must not manage credentials that outlive its confinement.",
133 "security": [
134 {
135 "bearerAuth": []
136 }
137 ],
138 "responses": {
139 "200": {
140 "description": "The account's keys. The public key body is never returned.",
141 "content": {
142 "application/json": {
143 "schema": {
144 "type": "object",
145 "properties": {
146 "items": {
147 "type": "array",
148 "items": {
149 "$ref": "#/components/schemas/Key"
150 }
151 },
152 "truncated": {
153 "type": "boolean",
154 "description": "Whether the result was cut short by a limit."
155 }
156 }
157 }
158 }
159 }
160 },
161 "401": {
162 "$ref": "#/components/responses/Unauthorized"
163 },
164 "403": {
165 "$ref": "#/components/responses/Forbidden"
166 }
167 }
168 },
169 "post": {
170 "tags": [
171 "keys"
172 ],
173 "operationId": "addKey",
174 "summary": "Register an SSH public key",
175 "security": [
176 {
177 "bearerAuth": []
178 }
179 ],
180 "requestBody": {
181 "required": true,
182 "content": {
183 "application/json": {
184 "schema": {
185 "$ref": "#/components/schemas/AddKey"
186 },
187 "examples": {
188 "laptop": {
189 "value": {
190 "title": "laptop",
191 "key": "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIJd8kRTIB0hLbrLZ0hBqUFwjBjHwzMbrUsGVLXCBYqEd",
192 "read_only": false,
193 "repos": []
194 }
195 }
196 }
197 }
198 }
199 },
200 "responses": {
201 "201": {
202 "description": "Registered.",
203 "headers": {
204 "Location": {
205 "schema": {
206 "type": "string"
207 }
208 }
209 },
210 "content": {
211 "application/json": {
212 "schema": {
213 "$ref": "#/components/schemas/Key"
214 }
215 }
216 }
217 },
218 "400": {
219 "description": "Not a supported public key. `authorized_keys` option prefixes such as `command=` are refused.",
220 "content": {
221 "application/problem+json": {
222 "schema": {
223 "$ref": "#/components/schemas/Problem"
224 }
225 }
226 }
227 },
228 "401": {
229 "$ref": "#/components/responses/Unauthorized"
230 },
231 "403": {
232 "$ref": "#/components/responses/Forbidden"
233 },
234 "409": {
235 "description": "That public key is already registered."
236 },
237 "429": {
238 "$ref": "#/components/responses/RateLimited"
239 }
240 }
241 }
242 },
243 "/v1/keys/{id}": {
244 "parameters": [
245 {
246 "name": "id",
247 "in": "path",
248 "required": true,
249 "schema": {
250 "type": "string"
251 }
252 }
253 ],
254 "delete": {
255 "tags": [
256 "keys"
257 ],
258 "operationId": "deleteKey",
259 "summary": "Remove an SSH public key",
260 "security": [
261 {
262 "bearerAuth": []
263 }
264 ],
265 "responses": {
266 "204": {
267 "description": "Removed."
268 },
269 "401": {
270 "$ref": "#/components/responses/Unauthorized"
271 },
272 "403": {
273 "$ref": "#/components/responses/Forbidden"
274 },
275 "404": {
276 "description": "No such key for this account."
277 }
278 }
279 }
280 },
281 "/v1/repos": {
282 "get": {
283 "tags": [
284 "repositories"
285 ],
286 "operationId": "listRepos",
287 "summary": "List repositories",
288 "security": [
289 {
290 "bearerAuth": []
291 }
292 ],
293 "responses": {
294 "200": {
295 "description": "Repositories this identity can read.",
296 "content": {
297 "application/json": {
298 "schema": {
299 "type": "object",
300 "properties": {
301 "items": {
302 "type": "array",
303 "items": {
304 "$ref": "#/components/schemas/Repository"
305 }
306 },
307 "truncated": {
308 "type": "boolean",
309 "description": "Whether the result was cut short by a limit."
310 }
311 }
312 }
313 }
314 }
315 },
316 "401": {
317 "$ref": "#/components/responses/Unauthorized"
318 },
319 "429": {
320 "$ref": "#/components/responses/RateLimited"
321 }
322 }
323 },
324 "post": {
325 "tags": [
326 "repositories"
327 ],
328 "operationId": "createRepo",
329 "summary": "Create a repository",
330 "security": [
331 {
332 "bearerAuth": []
333 }
334 ],
335 "requestBody": {
336 "required": true,
337 "content": {
338 "application/json": {
339 "schema": {
340 "$ref": "#/components/schemas/CreateRepo"
341 },
342 "examples": {
343 "minimal": {
344 "value": {
345 "name": "site"
346 }
347 },
348 "full": {
349 "value": {
350 "name": "site",
351 "default_branch": "refs/heads/main",
352 "description": "the shop"
353 }
354 }
355 }
356 }
357 }
358 },
359 "responses": {
360 "201": {
361 "description": "Created.",
362 "headers": {
363 "Location": {
364 "schema": {
365 "type": "string"
366 }
367 }
368 },
369 "content": {
370 "application/json": {
371 "schema": {
372 "$ref": "#/components/schemas/Repository"
373 }
374 }
375 }
376 },
377 "400": {
378 "description": "Invalid name or default branch.",
379 "content": {
380 "application/problem+json": {
381 "schema": {
382 "$ref": "#/components/schemas/Problem"
383 }
384 }
385 }
386 },
387 "401": {
388 "$ref": "#/components/responses/Unauthorized"
389 },
390 "409": {
391 "description": "A repository with that name exists. Names are case-folded, so `Site` collides with `site`."
392 },
393 "507": {
394 "description": "Below the disk reserve."
395 },
396 "429": {
397 "$ref": "#/components/responses/RateLimited"
398 },
399 "413": {
400 "description": "Repository quota exceeded. The problem body carries `limit` and `used`."
401 }
402 }
403 }
404 },
405 "/v1/repos/{account}/{repo}": {
406 "parameters": [
407 {
408 "$ref": "#/components/parameters/Account"
409 },
410 {
411 "$ref": "#/components/parameters/Repo"
412 }
413 ],
414 "get": {
415 "tags": [
416 "repositories"
417 ],
418 "operationId": "getRepo",
419 "summary": "Read a repository",
420 "description": "A repository the caller cannot read answers 404, not 403 \u2014 a 403 would confirm it exists.",
421 "security": [
422 {
423 "bearerAuth": []
424 }
425 ],
426 "responses": {
427 "200": {
428 "description": "The repository.",
429 "content": {
430 "application/json": {
431 "schema": {
432 "$ref": "#/components/schemas/Repository"
433 }
434 }
435 }
436 },
437 "401": {
438 "$ref": "#/components/responses/Unauthorized"
439 },
440 "404": {
441 "$ref": "#/components/responses/NotFound"
442 }
443 }
444 },
445 "delete": {
446 "tags": [
447 "repositories"
448 ],
449 "operationId": "deleteRepo",
450 "summary": "Delete a repository",
451 "description": "Moves the repository aside rather than removing it; it is recoverable until the sweep runs. Deleting an absent repository is 404, not 204 \u2014 an agent that typos a name must not be told it deleted something.",
452 "security": [
453 {
454 "bearerAuth": []
455 }
456 ],
457 "responses": {
458 "204": {
459 "description": "Deleted."
460 },
461 "401": {
462 "$ref": "#/components/responses/Unauthorized"
463 },
464 "403": {
465 "$ref": "#/components/responses/Forbidden"
466 },
467 "404": {
468 "$ref": "#/components/responses/NotFound"
469 }
470 }
471 },
472 "patch": {
473 "tags": [
474 "repositories"
475 ],
476 "operationId": "patchRepo",
477 "summary": "Update repository settings",
478 "description": "RFC 7396 merge patch: an absent field is unchanged, `null` clears.",
479 "security": [
480 {
481 "bearerAuth": []
482 }
483 ],
484 "requestBody": {
485 "required": true,
486 "content": {
487 "application/merge-patch+json": {
488 "schema": {
489 "type": "object",
490 "properties": {
491 "description": {
492 "type": [
493 "string",
494 "null"
495 ]
496 },
497 "default_branch": {
498 "type": "string",
499 "description": "Must already exist, or HEAD would point at nothing and clones would land detached."
500 },
501 "protected_refs": {
502 "type": "array",
503 "items": {
504 "type": "string"
505 },
506 "description": "Refs that may not be rewritten. Empty means none \u2014 rewriting is ordinary git and is allowed by default."
507 }
508 }
509 },
510 "examples": {
511 "protect the deploy branch": {
512 "value": {
513 "protected_refs": [
514 "refs/heads/main"
515 ]
516 }
517 },
518 "clear the description": {
519 "value": {
520 "description": null
521 }
522 }
523 }
524 }
525 }
526 },
527 "responses": {
528 "200": {
529 "description": "Updated.",
530 "content": {
531 "application/json": {
532 "schema": {
533 "$ref": "#/components/schemas/Repository"
534 }
535 }
536 }
537 },
538 "400": {
539 "description": "Invalid ref name."
540 },
541 "401": {
542 "$ref": "#/components/responses/Unauthorized"
543 },
544 "403": {
545 "$ref": "#/components/responses/Forbidden"
546 },
547 "404": {
548 "$ref": "#/components/responses/NotFound"
549 },
550 "409": {
551 "description": "`default_branch` does not exist in this repository."
552 }
553 }
554 }
555 },
556 "/v1/repos/{account}/{repo}/refs": {
557 "parameters": [
558 {
559 "$ref": "#/components/parameters/Account"
560 },
561 {
562 "$ref": "#/components/parameters/Repo"
563 }
564 ],
565 "get": {
566 "tags": [
567 "repositories"
568 ],
569 "operationId": "listRefs",
570 "summary": "List branches, tags and notes",
571 "security": [
572 {
573 "bearerAuth": []
574 }
575 ],
576 "responses": {
577 "200": {
578 "description": "Refs with their object ids.",
579 "content": {
580 "application/json": {
581 "schema": {
582 "type": "object",
583 "properties": {
584 "items": {
585 "type": "array",
586 "items": {
587 "$ref": "#/components/schemas/Ref"
588 }
589 },
590 "truncated": {
591 "type": "boolean",
592 "description": "Whether the result was cut short by a limit."
593 }
594 }
595 }
596 }
597 }
598 },
599 "401": {
600 "$ref": "#/components/responses/Unauthorized"
601 },
602 "404": {
603 "$ref": "#/components/responses/NotFound"
604 }
605 }
606 }
607 },
608 "/{account}/{repo}.git/info/refs": {
609 "parameters": [
610 {
611 "$ref": "#/components/parameters/Account"
612 },
613 {
614 "$ref": "#/components/parameters/Repo"
615 },
616 {
617 "name": "service",
618 "in": "query",
619 "required": true,
620 "description": "Exactly one occurrence. A repeated `service` parameter is refused, because this server and the CGI resolve duplicates differently and the authorization decision would not match the service that runs.",
621 "schema": {
622 "type": "string",
623 "enum": [
624 "git-upload-pack",
625 "git-receive-pack"
626 ]
627 }
628 }
629 ],
630 "get": {
631 "tags": [
632 "git"
633 ],
634 "operationId": "gitInfoRefs",
635 "summary": "Ref advertisement",
636 "description": "The git smart-HTTP handshake. Not REST, and errors are `text/plain`. `service=git-receive-pack` requires write access, because advertising for a push is itself a write signal.\n\nAuthentication is HTTP Basic: the username is ignored, the password is an API token.",
637 "security": [
638 {
639 "basicAuth": []
640 }
641 ],
642 "responses": {
643 "200": {
644 "description": "Ref advertisement.",
645 "content": {
646 "application/x-git-upload-pack-advertisement": {
647 "schema": {
648 "type": "string",
649 "format": "binary"
650 }
651 }
652 }
653 },
654 "401": {
655 "description": "Carries `WWW-Authenticate: Basic`, without which git fails instead of prompting.",
656 "content": {
657 "text/plain": {
658 "schema": {
659 "type": "string"
660 }
661 }
662 }
663 },
664 "403": {
665 "description": "Plain text, never problem+json."
666 },
667 "404": {
668 "description": "Plain text, never problem+json."
669 }
670 }
671 }
672 },
673 "/{account}/{repo}.git/git-upload-pack": {
674 "parameters": [
675 {
676 "$ref": "#/components/parameters/Account"
677 },
678 {
679 "$ref": "#/components/parameters/Repo"
680 }
681 ],
682 "post": {
683 "tags": [
684 "git"
685 ],
686 "operationId": "gitUploadPack",
687 "summary": "Fetch or clone",
688 "description": "Streamed end to end; nothing buffers the pack.",
689 "security": [
690 {
691 "basicAuth": []
692 }
693 ],
694 "requestBody": {
695 "required": true,
696 "content": {
697 "application/x-git-upload-pack-request": {
698 "schema": {
699 "type": "string",
700 "format": "binary"
701 }
702 }
703 }
704 },
705 "responses": {
706 "200": {
707 "description": "Packfile.",
708 "content": {
709 "application/x-git-upload-pack-result": {
710 "schema": {
711 "type": "string",
712 "format": "binary"
713 }
714 }
715 }
716 },
717 "401": {
718 "description": "Plain text with a Basic challenge."
719 },
720 "404": {
721 "description": "Plain text."
722 }
723 }
724 }
725 },
726 "/{account}/{repo}.git/git-receive-pack": {
727 "parameters": [
728 {
729 "$ref": "#/components/parameters/Account"
730 },
731 {
732 "$ref": "#/components/parameters/Repo"
733 }
734 ],
735 "post": {
736 "tags": [
737 "git"
738 ],
739 "operationId": "gitReceivePack",
740 "summary": "Push",
741 "description": "Push, including force-push. Rewriting a branch is allowed by default \u2014 `rebase`, `commit --amend` and `filter-branch` are ordinary git and this service does not block them; git's reflog is the recovery path. Protection is opt-in per ref via `protected_refs`, and is enforced identically over SSH.",
742 "security": [
743 {
744 "basicAuth": []
745 }
746 ],
747 "requestBody": {
748 "required": true,
749 "content": {
750 "application/x-git-receive-pack-request": {
751 "schema": {
752 "type": "string",
753 "format": "binary"
754 }
755 }
756 }
757 },
758 "responses": {
759 "200": {
760 "description": "Push report. A rejected ref update is reported inside this body, not as an HTTP status.",
761 "content": {
762 "application/x-git-receive-pack-result": {
763 "schema": {
764 "type": "string",
765 "format": "binary"
766 }
767 }
768 }
769 },
770 "401": {
771 "description": "Plain text with a Basic challenge."
772 },
773 "403": {
774 "description": "Plain text."
775 },
776 "413": {
777 "description": "Pack exceeds the maximum, or the account is over its storage quota. Over-quota blocks writes only; clones keep working."
778 },
779 "507": {
780 "description": "Below the disk reserve."
781 }
782 }
783 }
784 },
785 "/v1/repos/{account}/{repo}/tree/{path}": {
786 "parameters": [
787 {
788 "$ref": "#/components/parameters/Account"
789 },
790 {
791 "$ref": "#/components/parameters/Repo"
792 },
793 {
794 "name": "path",
795 "in": "path",
796 "required": true,
797 "allowReserved": true,
798 "description": "Directory within the tree. Empty for the root. Components equal to `.git` are refused in every case and Unicode variant.",
799 "schema": {
800 "type": "string"
801 }
802 },
803 {
804 "name": "ref",
805 "in": "query",
806 "required": false,
807 "description": "Fully-qualified ref (`refs/heads/main`) or a full object id. Defaults to the repository's default branch. A query parameter rather than a path segment because both refs and paths contain slashes, so `/raw/{ref}/{path}` has no unique parse.",
808 "schema": {
809 "type": "string"
810 }
811 }
812 ],
813 "get": {
814 "tags": [
815 "repositories"
816 ],
817 "operationId": "getTree",
818 "summary": "List a directory",
819 "security": [
820 {
821 "bearerAuth": []
822 }
823 ],
824 "responses": {
825 "200": {
826 "description": "Entries, with paths addressable as given.",
827 "content": {
828 "application/json": {
829 "schema": {
830 "type": "object",
831 "properties": {
832 "items": {
833 "type": "array",
834 "items": {
835 "$ref": "#/components/schemas/TreeEntry"
836 }
837 },
838 "truncated": {
839 "type": "boolean",
840 "description": "Whether the result was cut short by a limit."
841 }
842 }
843 }
844 }
845 }
846 },
847 "400": {
848 "description": "Invalid path or ref."
849 },
850 "401": {
851 "$ref": "#/components/responses/Unauthorized"
852 },
853 "404": {
854 "$ref": "#/components/responses/NotFound"
855 }
856 }
857 }
858 },
859 "/v1/repos/{account}/{repo}/raw/{path}": {
860 "parameters": [
861 {
862 "$ref": "#/components/parameters/Account"
863 },
864 {
865 "$ref": "#/components/parameters/Repo"
866 },
867 {
868 "name": "path",
869 "in": "path",
870 "required": true,
871 "allowReserved": true,
872 "schema": {
873 "type": "string"
874 }
875 },
876 {
877 "name": "ref",
878 "in": "query",
879 "required": false,
880 "description": "Fully-qualified ref (`refs/heads/main`) or a full object id. Defaults to the repository's default branch. A query parameter rather than a path segment because both refs and paths contain slashes, so `/raw/{ref}/{path}` has no unique parse.",
881 "schema": {
882 "type": "string"
883 }
884 }
885 ],
886 "get": {
887 "tags": [
888 "repositories"
889 ],
890 "operationId": "getRaw",
891 "summary": "Read a file's bytes",
892 "description": "Always `application/octet-stream` with `nosniff`, `Content-Disposition: attachment` and a sandbox CSP: this returns attacker-controlled bytes on the same origin as the API. Symlinks are refused rather than followed. Immutable when `ref` is a full object id.",
893 "security": [
894 {
895 "bearerAuth": []
896 }
897 ],
898 "responses": {
899 "200": {
900 "description": "File bytes.",
901 "headers": {
902 "ETag": {
903 "schema": {
904 "type": "string"
905 }
906 },
907 "Cache-Control": {
908 "schema": {
909 "type": "string"
910 }
911 }
912 },
913 "content": {
914 "application/octet-stream": {
915 "schema": {
916 "type": "string",
917 "format": "binary"
918 }
919 }
920 }
921 },
922 "400": {
923 "description": "Invalid path, or the target is a symlink."
924 },
925 "401": {
926 "$ref": "#/components/responses/Unauthorized"
927 },
928 "404": {
929 "$ref": "#/components/responses/NotFound"
930 },
931 "413": {
932 "description": "Blob exceeds the inline limit; fetch it over the git transport."
933 }
934 }
935 }
936 },
937 "/v1/repos/{account}/{repo}/commits": {
938 "parameters": [
939 {
940 "$ref": "#/components/parameters/Account"
941 },
942 {
943 "$ref": "#/components/parameters/Repo"
944 },
945 {
946 "name": "ref",
947 "in": "query",
948 "required": false,
949 "description": "Fully-qualified ref (`refs/heads/main`) or a full object id. Defaults to the repository's default branch. A query parameter rather than a path segment because both refs and paths contain slashes, so `/raw/{ref}/{path}` has no unique parse.",
950 "schema": {
951 "type": "string"
952 }
953 },
954 {
955 "name": "path",
956 "in": "query",
957 "required": false,
958 "description": "Limit the log to commits touching this path.",
959 "schema": {
960 "type": "string"
961 }
962 },
963 {
964 "name": "limit",
965 "in": "query",
966 "required": false,
967 "schema": {
968 "type": "integer",
969 "minimum": 1,
970 "maximum": 200,
971 "default": 50
972 }
973 }
974 ],
975 "get": {
976 "tags": [
977 "repositories"
978 ],
979 "operationId": "listCommits",
980 "summary": "Commit log",
981 "security": [
982 {
983 "bearerAuth": []
984 }
985 ],
986 "responses": {
987 "200": {
988 "description": "Commits, newest first.",
989 "content": {
990 "application/json": {
991 "schema": {
992 "type": "object",
993 "properties": {
994 "items": {
995 "type": "array",
996 "items": {
997 "$ref": "#/components/schemas/Commit"
998 }
999 },
1000 "truncated": {
1001 "type": "boolean",
1002 "description": "Whether the result was cut short by a limit."
1003 }
1004 }
1005 }
1006 }
1007 }
1008 },
1009 "400": {
1010 "description": "Invalid ref, path or limit."
1011 },
1012 "401": {
1013 "$ref": "#/components/responses/Unauthorized"
1014 },
1015 "404": {
1016 "$ref": "#/components/responses/NotFound"
1017 }
1018 }
1019 }
1020 },
1021 "/v1/repos/{account}/{repo}/commits/{sha}": {
1022 "parameters": [
1023 {
1024 "$ref": "#/components/parameters/Account"
1025 },
1026 {
1027 "$ref": "#/components/parameters/Repo"
1028 },
1029 {
1030 "name": "sha",
1031 "in": "path",
1032 "required": true,
1033 "description": "Full object id, 40 or 64 hex characters. Abbreviations and ref names are refused so a value cannot be read as a git argument.",
1034 "schema": {
1035 "type": "string",
1036 "pattern": "^[0-9a-f]{40,64}$"
1037 }
1038 }
1039 ],
1040 "get": {
1041 "tags": [
1042 "repositories"
1043 ],
1044 "operationId": "getCommit",
1045 "summary": "One commit and its changed paths",
1046 "security": [
1047 {
1048 "bearerAuth": []
1049 }
1050 ],
1051 "responses": {
1052 "200": {
1053 "description": "The commit plus its name-status summary.",
1054 "content": {
1055 "application/json": {
1056 "schema": {
1057 "type": "object",
1058 "properties": {
1059 "commit": {
1060 "$ref": "#/components/schemas/Commit"
1061 },
1062 "changed": {
1063 "type": "array",
1064 "items": {
1065 "type": "object",
1066 "properties": {
1067 "status": {
1068 "type": "string"
1069 },
1070 "path": {
1071 "type": "string"
1072 }
1073 }
1074 }
1075 }
1076 }
1077 }
1078 }
1079 }
1080 },
1081 "400": {
1082 "description": "Not a full object id."
1083 },
1084 "401": {
1085 "$ref": "#/components/responses/Unauthorized"
1086 },
1087 "404": {
1088 "$ref": "#/components/responses/NotFound"
1089 }
1090 }
1091 }
1092 },
1093 "/v1/repos/{account}/{repo}/compare": {
1094 "parameters": [
1095 {
1096 "$ref": "#/components/parameters/Account"
1097 },
1098 {
1099 "$ref": "#/components/parameters/Repo"
1100 },
1101 {
1102 "name": "base",
1103 "in": "query",
1104 "required": true,
1105 "schema": {
1106 "type": "string"
1107 }
1108 },
1109 {
1110 "name": "head",
1111 "in": "query",
1112 "required": true,
1113 "schema": {
1114 "type": "string"
1115 }
1116 }
1117 ],
1118 "get": {
1119 "tags": [
1120 "repositories"
1121 ],
1122 "operationId": "compare",
1123 "summary": "What changed between two points",
1124 "description": "Diff from the merge base, matching `git diff base...head` \u2014 'what head added', not 'how the two differ'. Both endpoints are query parameters because refs contain slashes.",
1125 "security": [
1126 {
1127 "bearerAuth": []
1128 }
1129 ],
1130 "responses": {
1131 "200": {
1132 "description": "Commits, changed paths and line counts.",
1133 "content": {
1134 "application/json": {
1135 "schema": {
1136 "type": "object",
1137 "properties": {
1138 "base": {
1139 "type": "string"
1140 },
1141 "head": {
1142 "type": "string"
1143 },
1144 "merge_base": {
1145 "type": [
1146 "string",
1147 "null"
1148 ]
1149 },
1150 "commits": {
1151 "type": "array",
1152 "items": {
1153 "$ref": "#/components/schemas/Commit"
1154 }
1155 },
1156 "changed": {
1157 "type": "array",
1158 "items": {
1159 "$ref": "#/components/schemas/Change"
1160 }
1161 },
1162 "additions": {
1163 "type": "integer"
1164 },
1165 "deletions": {
1166 "type": "integer"
1167 }
1168 }
1169 }
1170 }
1171 }
1172 },
1173 "400": {
1174 "description": "Missing or invalid revision."
1175 },
1176 "401": {
1177 "$ref": "#/components/responses/Unauthorized"
1178 },
1179 "404": {
1180 "$ref": "#/components/responses/NotFound"
1181 }
1182 }
1183 }
1184 },
1185 "/v1/repos/{account}/{repo}/search": {
1186 "parameters": [
1187 {
1188 "$ref": "#/components/parameters/Account"
1189 },
1190 {
1191 "$ref": "#/components/parameters/Repo"
1192 },
1193 {
1194 "name": "q",
1195 "in": "query",
1196 "required": true,
1197 "schema": {
1198 "type": "string"
1199 },
1200 "description": "Literal text, not a regular expression. Passed after `-e` so it cannot be read as a flag."
1201 },
1202 {
1203 "name": "path",
1204 "in": "query",
1205 "schema": {
1206 "type": "string"
1207 }
1208 },
1209 {
1210 "name": "ref",
1211 "in": "query",
1212 "schema": {
1213 "type": "string"
1214 },
1215 "description": "Fully-qualified ref or full object id. Defaults to the repository's default branch."
1216 },
1217 {
1218 "name": "limit",
1219 "in": "query",
1220 "schema": {
1221 "type": "integer"
1222 }
1223 }
1224 ],
1225 "get": {
1226 "tags": [
1227 "repositories"
1228 ],
1229 "operationId": "search",
1230 "summary": "Search file contents",
1231 "description": "Binary files are skipped. No matches is a 200 with an empty list, not a 404.",
1232 "security": [
1233 {
1234 "bearerAuth": []
1235 }
1236 ],
1237 "responses": {
1238 "200": {
1239 "description": "Matches with file, line number and content.",
1240 "content": {
1241 "application/json": {
1242 "schema": {
1243 "type": "object",
1244 "properties": {
1245 "items": {
1246 "type": "array",
1247 "items": {
1248 "type": "object",
1249 "properties": {
1250 "path": {
1251 "type": "string"
1252 },
1253 "line": {
1254 "type": "integer"
1255 },
1256 "content": {
1257 "type": "string"
1258 }
1259 }
1260 }
1261 },
1262 "truncated": {
1263 "type": "boolean"
1264 }
1265 }
1266 }
1267 }
1268 }
1269 },
1270 "400": {
1271 "description": "`q` missing or empty."
1272 },
1273 "401": {
1274 "$ref": "#/components/responses/Unauthorized"
1275 },
1276 "404": {
1277 "$ref": "#/components/responses/NotFound"
1278 }
1279 }
1280 }
1281 },
1282 "/v1/repos/{account}/{repo}/blame/{path}": {
1283 "parameters": [
1284 {
1285 "$ref": "#/components/parameters/Account"
1286 },
1287 {
1288 "$ref": "#/components/parameters/Repo"
1289 },
1290 {
1291 "name": "path",
1292 "in": "path",
1293 "required": true,
1294 "allowReserved": true,
1295 "schema": {
1296 "type": "string"
1297 }
1298 },
1299 {
1300 "name": "ref",
1301 "in": "query",
1302 "schema": {
1303 "type": "string"
1304 },
1305 "description": "Fully-qualified ref or full object id. Defaults to the repository's default branch."
1306 }
1307 ],
1308 "get": {
1309 "tags": [
1310 "repositories"
1311 ],
1312 "operationId": "blame",
1313 "summary": "Who last changed each line",
1314 "security": [
1315 {
1316 "bearerAuth": []
1317 }
1318 ],
1319 "responses": {
1320 "200": {
1321 "description": "Every line with its commit, author and time.",
1322 "content": {
1323 "application/json": {
1324 "schema": {
1325 "type": "object",
1326 "properties": {
1327 "path": {
1328 "type": "string"
1329 },
1330 "ref": {
1331 "type": "string"
1332 },
1333 "items": {
1334 "type": "array",
1335 "items": {
1336 "type": "object",
1337 "properties": {
1338 "line": {
1339 "type": "integer"
1340 },
1341 "sha": {
1342 "type": "string"
1343 },
1344 "author": {
1345 "type": "string"
1346 },
1347 "authored_at": {
1348 "type": "integer"
1349 },
1350 "content": {
1351 "type": "string"
1352 }
1353 }
1354 }
1355 }
1356 }
1357 }
1358 }
1359 }
1360 },
1361 "400": {
1362 "description": "Invalid path or ref."
1363 },
1364 "401": {
1365 "$ref": "#/components/responses/Unauthorized"
1366 },
1367 "404": {
1368 "$ref": "#/components/responses/NotFound"
1369 }
1370 }
1371 }
1372 },
1373 "/mcp": {
1374 "post": {
1375 "tags": [
1376 "mcp"
1377 ],
1378 "operationId": "mcp",
1379 "summary": "MCP JSON-RPC endpoint",
1380 "description": "Model Context Protocol over JSON-RPC 2.0. Methods: `initialize`, `ping`, `tools/list`, `tools/call`. Tools cover administration (repositories, SSH keys) and discovery (refs, tree, file, search, log, compare, blame).\n\nThere is no tool that writes to a repository \u2014 an agent changes code by cloning with git and pushing. A tool failure caused by the caller is returned as a tool result with `isError: true` rather than as a JSON-RPC error, so the model sees it and adapts.\n\nA notification (no `id`) is answered with 202 and no body.",
1381 "security": [
1382 {
1383 "bearerAuth": []
1384 }
1385 ],
1386 "requestBody": {
1387 "required": true,
1388 "content": {
1389 "application/json": {
1390 "schema": {
1391 "type": "object",
1392 "required": [
1393 "jsonrpc",
1394 "method"
1395 ],
1396 "properties": {
1397 "jsonrpc": {
1398 "type": "string",
1399 "const": "2.0"
1400 },
1401 "id": {
1402 "type": [
1403 "string",
1404 "integer",
1405 "null"
1406 ]
1407 },
1408 "method": {
1409 "type": "string"
1410 },
1411 "params": {
1412 "type": "object"
1413 }
1414 }
1415 },
1416 "examples": {
1417 "list tools": {
1418 "value": {
1419 "jsonrpc": "2.0",
1420 "id": 1,
1421 "method": "tools/list"
1422 }
1423 },
1424 "create a repository": {
1425 "value": {
1426 "jsonrpc": "2.0",
1427 "id": 2,
1428 "method": "tools/call",
1429 "params": {
1430 "name": "repo_create",
1431 "arguments": {
1432 "name": "shop"
1433 }
1434 }
1435 }
1436 },
1437 "search": {
1438 "value": {
1439 "jsonrpc": "2.0",
1440 "id": 3,
1441 "method": "tools/call",
1442 "params": {
1443 "name": "search",
1444 "arguments": {
1445 "repo": "shop",
1446 "q": "TODO"
1447 }
1448 }
1449 }
1450 }
1451 }
1452 }
1453 }
1454 },
1455 "responses": {
1456 "200": {
1457 "description": "JSON-RPC response.",
1458 "content": {
1459 "application/json": {
1460 "schema": {
1461 "type": "object"
1462 }
1463 }
1464 }
1465 },
1466 "202": {
1467 "description": "Notification accepted; no body."
1468 },
1469 "401": {
1470 "$ref": "#/components/responses/Unauthorized"
1471 },
1472 "429": {
1473 "$ref": "#/components/responses/RateLimited"
1474 }
1475 }
1476 }
1477 },
1478 "/v1/repos/{account}/{repo}/runs": {
1479 "parameters": [
1480 {
1481 "$ref": "#/components/parameters/Account"
1482 },
1483 {
1484 "$ref": "#/components/parameters/Repo"
1485 }
1486 ],
1487 "get": {
1488 "tags": [
1489 "ci"
1490 ],
1491 "operationId": "listRuns",
1492 "summary": "CI runs, newest first",
1493 "parameters": [
1494 {
1495 "name": "status",
1496 "in": "query",
1497 "schema": {
1498 "$ref": "#/components/schemas/RunStatus"
1499 }
1500 }
1501 ],
1502 "security": [
1503 {
1504 "bearerAuth": []
1505 }
1506 ],
1507 "responses": {
1508 "200": {
1509 "description": "Runs.",
1510 "content": {
1511 "application/json": {
1512 "schema": {
1513 "type": "object",
1514 "properties": {
1515 "items": {
1516 "type": "array",
1517 "items": {
1518 "$ref": "#/components/schemas/Run"
1519 }
1520 },
1521 "truncated": {
1522 "type": "boolean"
1523 }
1524 }
1525 }
1526 }
1527 }
1528 },
1529 "401": {
1530 "$ref": "#/components/responses/Unauthorized"
1531 },
1532 "404": {
1533 "$ref": "#/components/responses/NotFound"
1534 },
1535 "429": {
1536 "$ref": "#/components/responses/RateLimited"
1537 }
1538 }
1539 },
1540 "post": {
1541 "tags": [
1542 "ci"
1543 ],
1544 "operationId": "triggerRun",
1545 "summary": "Queue a run by hand",
1546 "description": "Queues a run for a ref that is already pushed. Requires CI to be enabled.",
1547 "security": [
1548 {
1549 "bearerAuth": []
1550 }
1551 ],
1552 "requestBody": {
1553 "required": false,
1554 "content": {
1555 "application/json": {
1556 "schema": {
1557 "type": "object",
1558 "properties": {
1559 "ref": {
1560 "type": "string",
1561 "description": "Defaults to the repository's default branch."
1562 }
1563 }
1564 }
1565 }
1566 }
1567 },
1568 "responses": {
1569 "201": {
1570 "description": "Queued.",
1571 "content": {
1572 "application/json": {
1573 "schema": {
1574 "$ref": "#/components/schemas/Run"
1575 }
1576 }
1577 }
1578 },
1579 "401": {
1580 "$ref": "#/components/responses/Unauthorized"
1581 },
1582 "404": {
1583 "$ref": "#/components/responses/NotFound"
1584 },
1585 "409": {
1586 "description": "CI is disabled, or the ref does not exist."
1587 },
1588 "429": {
1589 "$ref": "#/components/responses/RateLimited"
1590 }
1591 }
1592 }
1593 },
1594 "/v1/repos/{account}/{repo}/runs/{id}": {
1595 "parameters": [
1596 {
1597 "$ref": "#/components/parameters/Account"
1598 },
1599 {
1600 "$ref": "#/components/parameters/Repo"
1601 },
1602 {
1603 "name": "id",
1604 "in": "path",
1605 "required": true,
1606 "schema": {
1607 "type": "string"
1608 }
1609 }
1610 ],
1611 "get": {
1612 "tags": [
1613 "ci"
1614 ],
1615 "operationId": "getRun",
1616 "summary": "One run",
1617 "security": [
1618 {
1619 "bearerAuth": []
1620 }
1621 ],
1622 "responses": {
1623 "200": {
1624 "description": "The run.",
1625 "content": {
1626 "application/json": {
1627 "schema": {
1628 "$ref": "#/components/schemas/Run"
1629 }
1630 }
1631 }
1632 },
1633 "401": {
1634 "$ref": "#/components/responses/Unauthorized"
1635 },
1636 "404": {
1637 "$ref": "#/components/responses/NotFound"
1638 }
1639 }
1640 },
1641 "patch": {
1642 "tags": [
1643 "ci"
1644 ],
1645 "operationId": "cancelRun",
1646 "summary": "Cancel a run",
1647 "description": "The only supported transition is to `cancelled`. Not `DELETE`: the run still exists and stays readable afterwards.",
1648 "security": [
1649 {
1650 "bearerAuth": []
1651 }
1652 ],
1653 "requestBody": {
1654 "required": true,
1655 "content": {
1656 "application/json": {
1657 "schema": {
1658 "type": "object",
1659 "required": [
1660 "status"
1661 ],
1662 "properties": {
1663 "status": {
1664 "type": "string",
1665 "enum": [
1666 "cancelled"
1667 ]
1668 }
1669 }
1670 }
1671 }
1672 }
1673 },
1674 "responses": {
1675 "200": {
1676 "description": "Cancelled.",
1677 "content": {
1678 "application/json": {
1679 "schema": {
1680 "$ref": "#/components/schemas/Run"
1681 }
1682 }
1683 }
1684 },
1685 "400": {
1686 "description": "Unsupported transition."
1687 },
1688 "401": {
1689 "$ref": "#/components/responses/Unauthorized"
1690 },
1691 "404": {
1692 "$ref": "#/components/responses/NotFound"
1693 },
1694 "409": {
1695 "description": "The run already finished."
1696 }
1697 }
1698 }
1699 },
1700 "/v1/repos/{account}/{repo}/runs/{id}/logs": {
1701 "parameters": [
1702 {
1703 "$ref": "#/components/parameters/Account"
1704 },
1705 {
1706 "$ref": "#/components/parameters/Repo"
1707 },
1708 {
1709 "name": "id",
1710 "in": "path",
1711 "required": true,
1712 "schema": {
1713 "type": "string"
1714 }
1715 }
1716 ],
1717 "get": {
1718 "tags": [
1719 "ci"
1720 ],
1721 "operationId": "getRunLogs",
1722 "summary": "Captured output of a run",
1723 "description": "Plain text, capped at the configured size. Cacheable only once the run is finished.",
1724 "security": [
1725 {
1726 "bearerAuth": []
1727 }
1728 ],
1729 "responses": {
1730 "200": {
1731 "description": "Output so far.",
1732 "content": {
1733 "text/plain": {
1734 "schema": {
1735 "type": "string"
1736 }
1737 }
1738 }
1739 },
1740 "401": {
1741 "$ref": "#/components/responses/Unauthorized"
1742 },
1743 "404": {
1744 "$ref": "#/components/responses/NotFound"
1745 }
1746 }
1747 }
1748 }
1749 },
1750 "components": {
1751 "securitySchemes": {
1752 "bearerAuth": {
1753 "type": "http",
1754 "scheme": "bearer",
1755 "description": "An API token. Scopes: `repo:read`, `repo:write`, `admin`, `ci`. Tokens expire; 90 days is the default."
1756 },
1757 "basicAuth": {
1758 "type": "http",
1759 "scheme": "basic",
1760 "description": "Git over HTTP. The username is ignored; the password is an API token."
1761 }
1762 },
1763 "parameters": {
1764 "Account": {
1765 "name": "account",
1766 "in": "path",
1767 "required": true,
1768 "description": "Case-folded account name.",
1769 "schema": {
1770 "$ref": "#/components/schemas/Name"
1771 }
1772 },
1773 "Repo": {
1774 "name": "repo",
1775 "in": "path",
1776 "required": true,
1777 "description": "Case-folded repository name.",
1778 "schema": {
1779 "$ref": "#/components/schemas/Name"
1780 }
1781 }
1782 },
1783 "responses": {
1784 "Unauthorized": {
1785 "description": "Missing or invalid credential.",
1786 "content": {
1787 "application/problem+json": {
1788 "schema": {
1789 "$ref": "#/components/schemas/Problem"
1790 }
1791 }
1792 }
1793 },
1794 "Forbidden": {
1795 "description": "The credential is valid but lacks the scope.",
1796 "content": {
1797 "application/problem+json": {
1798 "schema": {
1799 "$ref": "#/components/schemas/Problem"
1800 }
1801 }
1802 }
1803 },
1804 "NotFound": {
1805 "description": "No such resource, or the caller may not read it.",
1806 "content": {
1807 "application/problem+json": {
1808 "schema": {
1809 "$ref": "#/components/schemas/Problem"
1810 }
1811 }
1812 }
1813 },
1814 "RateLimited": {
1815 "description": "Too many requests. Carries `Retry-After`. The git transport is not rate limited.",
1816 "headers": {
1817 "Retry-After": {
1818 "schema": {
1819 "type": "integer"
1820 }
1821 }
1822 },
1823 "content": {
1824 "application/problem+json": {
1825 "schema": {
1826 "$ref": "#/components/schemas/Problem"
1827 }
1828 }
1829 }
1830 }
1831 },
1832 "schemas": {
1833 "Name": {
1834 "type": "string",
1835 "minLength": 1,
1836 "maxLength": 64,
1837 "pattern": "^[A-Za-z0-9][A-Za-z0-9._-]*$",
1838 "description": "Case-folded on receipt. Must not contain `..`, end in `.lock`, or be a reserved word."
1839 },
1840 "Health": {
1841 "type": "object",
1842 "required": [
1843 "status",
1844 "mode",
1845 "version"
1846 ],
1847 "properties": {
1848 "status": {
1849 "type": "string",
1850 "enum": [
1851 "ok",
1852 "degraded"
1853 ]
1854 },
1855 "mode": {
1856 "type": "string",
1857 "enum": [
1858 "standalone",
1859 "control",
1860 "tenant"
1861 ]
1862 },
1863 "version": {
1864 "type": "string"
1865 },
1866 "disk_free_bytes": {
1867 "type": [
1868 "integer",
1869 "null"
1870 ]
1871 },
1872 "disk_reserve_bytes": {
1873 "type": "integer"
1874 },
1875 "git_slots_available": {
1876 "type": "integer",
1877 "description": "Remaining concurrent git subprocess slots."
1878 },
1879 "rate_limit_per_minute": {
1880 "type": "integer"
1881 }
1882 }
1883 },
1884 "Account": {
1885 "type": "object",
1886 "required": [
1887 "account",
1888 "scopes",
1889 "repos"
1890 ],
1891 "properties": {
1892 "account": {
1893 "$ref": "#/components/schemas/Name"
1894 },
1895 "scopes": {
1896 "type": "array",
1897 "items": {
1898 "type": "string",
1899 "enum": [
1900 "repo:read",
1901 "repo:write",
1902 "admin",
1903 "ci"
1904 ]
1905 }
1906 },
1907 "repos": {
1908 "type": "array",
1909 "items": {
1910 "$ref": "#/components/schemas/Name"
1911 },
1912 "description": "Empty means every repository the account owns."
1913 },
1914 "usage": {
1915 "type": "object",
1916 "properties": {
1917 "repos": {
1918 "type": "integer"
1919 },
1920 "bytes": {
1921 "type": "integer",
1922 "description": "As git accounts for it: loose objects plus packs."
1923 }
1924 }
1925 },
1926 "limits": {
1927 "type": "object",
1928 "description": "Zero means the limit is disabled.",
1929 "properties": {
1930 "max_repos": {
1931 "type": "integer"
1932 },
1933 "max_repo_bytes": {
1934 "type": "integer"
1935 },
1936 "max_account_bytes": {
1937 "type": "integer"
1938 },
1939 "rate_per_minute": {
1940 "type": "integer"
1941 }
1942 }
1943 }
1944 }
1945 },
1946 "CreateRepo": {
1947 "type": "object",
1948 "required": [
1949 "name"
1950 ],
1951 "properties": {
1952 "name": {
1953 "$ref": "#/components/schemas/Name"
1954 },
1955 "default_branch": {
1956 "type": "string",
1957 "default": "refs/heads/main",
1958 "description": "Fully qualified and under `refs/heads/`."
1959 },
1960 "description": {
1961 "type": "string"
1962 },
1963 "protected_refs": {
1964 "type": "array",
1965 "items": {
1966 "type": "string"
1967 },
1968 "default": []
1969 },
1970 "import_url": {
1971 "type": "string",
1972 "description": "Seed the repository from a public https/http remote. Refused for URLs embedding credentials or resolving to loopback, private or link-local addresses."
1973 }
1974 }
1975 },
1976 "Repository": {
1977 "type": "object",
1978 "required": [
1979 "account",
1980 "name",
1981 "default_branch",
1982 "created_at"
1983 ],
1984 "properties": {
1985 "account": {
1986 "$ref": "#/components/schemas/Name"
1987 },
1988 "name": {
1989 "$ref": "#/components/schemas/Name"
1990 },
1991 "display_name": {
1992 "type": "string",
1993 "description": "The name as typed. Never a path component."
1994 },
1995 "default_branch": {
1996 "type": "string"
1997 },
1998 "description": {
1999 "type": "string"
2000 },
2001 "created_at": {
2002 "type": "integer",
2003 "description": "Unix seconds."
2004 },
2005 "clone_url_http": {
2006 "type": "string"
2007 },
2008 "clone_url_ssh": {
2009 "type": "string"
2010 },
2011 "protected_refs": {
2012 "type": "array",
2013 "items": {
2014 "type": "string"
2015 },
2016 "description": "Refs that may not be rewritten. Empty by default."
2017 }
2018 }
2019 },
2020 "Ref": {
2021 "type": "object",
2022 "required": [
2023 "name",
2024 "sha",
2025 "type"
2026 ],
2027 "properties": {
2028 "name": {
2029 "type": "string",
2030 "description": "Fully qualified, e.g. `refs/heads/main`."
2031 },
2032 "sha": {
2033 "type": "string",
2034 "pattern": "^[0-9a-f]{40,64}$"
2035 },
2036 "type": {
2037 "type": "string",
2038 "enum": [
2039 "branch",
2040 "tag",
2041 "note"
2042 ]
2043 },
2044 "updated_at": {
2045 "type": "integer"
2046 },
2047 "subject": {
2048 "type": "string"
2049 }
2050 }
2051 },
2052 "AddKey": {
2053 "type": "object",
2054 "required": [
2055 "title",
2056 "key"
2057 ],
2058 "properties": {
2059 "title": {
2060 "type": "string",
2061 "minLength": 1,
2062 "maxLength": 128
2063 },
2064 "key": {
2065 "type": "string",
2066 "description": "One `authorized_keys` line. Option prefixes such as `command=` are refused. Supported: ssh-ed25519, ecdsa-sha2-nistp256/384/521, ssh-rsa."
2067 },
2068 "read_only": {
2069 "type": "boolean",
2070 "default": false,
2071 "description": "A read-only key may clone and fetch but not push."
2072 },
2073 "repos": {
2074 "type": "array",
2075 "items": {
2076 "$ref": "#/components/schemas/Name"
2077 },
2078 "description": "Confines the key. Empty means every repository the account owns."
2079 }
2080 }
2081 },
2082 "Key": {
2083 "type": "object",
2084 "required": [
2085 "id",
2086 "title",
2087 "fingerprint",
2088 "read_only",
2089 "created_at"
2090 ],
2091 "properties": {
2092 "id": {
2093 "type": "string"
2094 },
2095 "title": {
2096 "type": "string"
2097 },
2098 "fingerprint": {
2099 "type": "string",
2100 "description": "`SHA256:...`, matching `ssh-keygen -lf`."
2101 },
2102 "read_only": {
2103 "type": "boolean"
2104 },
2105 "repos": {
2106 "type": "array",
2107 "items": {
2108 "$ref": "#/components/schemas/Name"
2109 }
2110 },
2111 "created_at": {
2112 "type": "integer"
2113 },
2114 "last_used_at": {
2115 "type": [
2116 "integer",
2117 "null"
2118 ]
2119 }
2120 }
2121 },
2122 "Problem": {
2123 "type": "object",
2124 "description": "RFC 9457. Discriminate on `type`; `detail` is prose and may change.",
2125 "required": [
2126 "type",
2127 "title",
2128 "status"
2129 ],
2130 "properties": {
2131 "type": {
2132 "type": "string",
2133 "format": "uri",
2134 "description": "`https://zuka.dev/problems/{slug}`.",
2135 "examples": [
2136 "https://zuka.dev/problems/invalid-name",
2137 "https://zuka.dev/problems/invalid-ref",
2138 "https://zuka.dev/problems/repo-exists",
2139 "https://zuka.dev/problems/non-fast-forward",
2140 "https://zuka.dev/problems/not-found",
2141 "https://zuka.dev/problems/storage-full",
2142 "https://zuka.dev/problems/quota-exceeded",
2143 "https://zuka.dev/problems/rate-limited"
2144 ]
2145 },
2146 "title": {
2147 "type": "string"
2148 },
2149 "status": {
2150 "type": "integer"
2151 },
2152 "detail": {
2153 "type": "string"
2154 },
2155 "current_sha": {
2156 "type": "string",
2157 "description": "On a ref conflict, the sha the ref actually points at."
2158 },
2159 "limit": {
2160 "type": "integer"
2161 },
2162 "used": {
2163 "type": "integer",
2164 "description": "On a quota problem, current usage against `limit`."
2165 },
2166 "retry_after": {
2167 "type": "integer",
2168 "description": "On a rate-limit problem, seconds until the next request is allowed."
2169 }
2170 }
2171 },
2172 "TreeEntry": {
2173 "type": "object",
2174 "required": [
2175 "path",
2176 "name",
2177 "type",
2178 "mode",
2179 "sha"
2180 ],
2181 "properties": {
2182 "path": {
2183 "type": "string",
2184 "description": "Full path from the repository root."
2185 },
2186 "name": {
2187 "type": "string"
2188 },
2189 "type": {
2190 "type": "string",
2191 "enum": [
2192 "file",
2193 "dir",
2194 "symlink",
2195 "submodule"
2196 ]
2197 },
2198 "mode": {
2199 "type": "string",
2200 "examples": [
2201 "100644",
2202 "100755",
2203 "040000"
2204 ]
2205 },
2206 "sha": {
2207 "type": "string"
2208 },
2209 "size": {
2210 "type": [
2211 "integer",
2212 "null"
2213 ]
2214 }
2215 }
2216 },
2217 "Commit": {
2218 "type": "object",
2219 "required": [
2220 "sha",
2221 "message",
2222 "author_name",
2223 "authored_at",
2224 "parents"
2225 ],
2226 "properties": {
2227 "sha": {
2228 "type": "string"
2229 },
2230 "message": {
2231 "type": "string"
2232 },
2233 "author_name": {
2234 "type": "string"
2235 },
2236 "author_email": {
2237 "type": "string"
2238 },
2239 "authored_at": {
2240 "type": "integer",
2241 "description": "Unix seconds."
2242 },
2243 "parents": {
2244 "type": "array",
2245 "items": {
2246 "type": "string"
2247 }
2248 }
2249 }
2250 },
2251 "Change": {
2252 "type": "object",
2253 "properties": {
2254 "status": {
2255 "type": "string",
2256 "description": "git name-status letter, e.g. M, A, D."
2257 },
2258 "path": {
2259 "type": "string"
2260 }
2261 }
2262 },
2263 "RunStatus": {
2264 "type": "string",
2265 "enum": [
2266 "queued",
2267 "running",
2268 "succeeded",
2269 "failed",
2270 "cancelled",
2271 "timed_out"
2272 ]
2273 },
2274 "Run": {
2275 "type": "object",
2276 "required": [
2277 "id",
2278 "account",
2279 "repo",
2280 "git_ref",
2281 "sha",
2282 "status",
2283 "created_at"
2284 ],
2285 "properties": {
2286 "id": {
2287 "type": "string"
2288 },
2289 "account": {
2290 "type": "string"
2291 },
2292 "repo": {
2293 "type": "string"
2294 },
2295 "git_ref": {
2296 "type": "string"
2297 },
2298 "sha": {
2299 "type": "string"
2300 },
2301 "status": {
2302 "$ref": "#/components/schemas/RunStatus"
2303 },
2304 "created_at": {
2305 "type": "integer"
2306 },
2307 "started_at": {
2308 "type": [
2309 "integer",
2310 "null"
2311 ]
2312 },
2313 "finished_at": {
2314 "type": [
2315 "integer",
2316 "null"
2317 ]
2318 },
2319 "exit_code": {
2320 "type": [
2321 "integer",
2322 "null"
2323 ]
2324 },
2325 "detail": {
2326 "type": [
2327 "string",
2328 "null"
2329 ]
2330 }
2331 }
2332 }
2333 }
2334 }
2335}