{
    "openapi": "3.1.0",
    "info": {
        "title": "MAGDOX Code Security CI API",
        "version": "2026-10-12",
        "summary": "Send code from any CI system, follow the scan, gate the pipeline and fetch the run's exports.",
        "description": "Every call is POST with a JSON body, except the upload, whose body is the archive. Nothing goes in the address: a query string is refused with 400 and ids travel in the body. Authenticate with a CI token from the console (Settings, Integrations, CI and pull requests) as a Bearer token. Answers carry metadata and findings with rule, severity, file and line; the code you upload is deleted when the scan ends, unless an admin keeps uploads for proofs, for 24 hours at most."
    },
    "servers": [
        {
            "url": "https://api.magdox.io"
        }
    ],
    "security": [
        {
            "ciToken": []
        }
    ],
    "components": {
        "securitySchemes": {
            "ciToken": {
                "type": "http",
                "scheme": "bearer",
                "description": "A CI token (mgdx_ci_...). Keep it in your CI system's secret store."
            }
        },
        "schemas": {
            "Id": {
                "type": "object",
                "required": [
                    "id"
                ],
                "properties": {
                    "id": {
                        "type": "string",
                        "format": "uuid"
                    }
                },
                "additionalProperties": false
            },
            "Error": {
                "type": "object",
                "required": [
                    "error",
                    "code"
                ],
                "properties": {
                    "error": {
                        "type": "string",
                        "description": "One plain sentence saying what was wrong and what to send instead."
                    },
                    "code": {
                        "type": "string",
                        "enum": [
                            "invalid_request",
                            "unauthenticated",
                            "forbidden",
                            "not_found",
                            "method_not_allowed",
                            "conflict",
                            "too_large",
                            "unsupported_media_type",
                            "rate_limited",
                            "unavailable",
                            "internal_error",
                            "error"
                        ]
                    },
                    "request_id": {
                        "type": "string",
                        "description": "Quote it to support: it finds the request in the server's log."
                    }
                }
            },
            "ScanJob": {
                "type": "object",
                "properties": {
                    "id": {
                        "type": "string",
                        "format": "uuid"
                    },
                    "project": {
                        "type": "string",
                        "description": "The repository key."
                    },
                    "source": {
                        "type": "object",
                        "description": "Where the code came from. An upload names the branch (ref), commit and pull request its headers sent.",
                        "required": [
                            "kind"
                        ],
                        "properties": {
                            "kind": {
                                "type": "string",
                                "enum": [
                                    "upload",
                                    "github",
                                    "gitlab",
                                    "bitbucket",
                                    "azure"
                                ]
                            },
                            "provider": {
                                "type": "string",
                                "enum": [
                                    "github",
                                    "gitlab",
                                    "bitbucket"
                                ],
                                "description": "With a pull request: the code host it is on."
                            },
                            "repo": {
                                "type": "string",
                                "description": "With a pull request: the repository at the provider, owner/name."
                            },
                            "ref": {
                                "type": "string",
                                "description": "The branch, decoded."
                            },
                            "commit": {
                                "type": "string"
                            },
                            "pull_request": {
                                "type": "integer",
                                "minimum": 1
                            }
                        }
                    },
                    "status": {
                        "type": "string",
                        "enum": [
                            "awaiting_upload",
                            "queued",
                            "running",
                            "succeeded",
                            "failed",
                            "cancelled"
                        ],
                        "description": "awaiting_upload: a staged job whose archive has not been started yet (/v1/scan-jobs/start)."
                    },
                    "error_code": {
                        "type": "string",
                        "enum": [
                            "archive_rejected",
                            "checksum_mismatch",
                            "source_unavailable",
                            "too_large",
                            "scan_failed",
                            "upload_failed",
                            "timeout",
                            "timed_out",
                            "upload_not_received",
                            "worker_exited",
                            "licence_required",
                            "internal"
                        ],
                        "description": "When failed: why, as a short code. archive_rejected: the archive broke a rule (an unsafe path, a device, a limit); timed_out: the scan ran past its time; upload_not_received: a staged archive never arrived."
                    },
                    "run_id": {
                        "type": "string",
                        "description": "When succeeded: the run to read with /v1/read/runs and its exports with /v1/runs/artifacts."
                    },
                    "links_skipped": {
                        "type": "integer",
                        "minimum": 0,
                        "description": "Symbolic and hard links in the archive, skipped and never followed."
                    },
                    "code_size": {
                        "type": "integer",
                        "minimum": 0,
                        "description": "The archive's size in bytes."
                    },
                    "code_sha256": {
                        "type": "string",
                        "pattern": "^[0-9a-f]{64}$"
                    },
                    "code_deleted_at": {
                        "type": "string",
                        "format": "date-time",
                        "description": "When the archive was deleted, once the scan ended."
                    },
                    "created_at": {
                        "type": "string",
                        "format": "date-time"
                    },
                    "started_at": {
                        "type": "string",
                        "format": "date-time"
                    },
                    "finished_at": {
                        "type": "string",
                        "format": "date-time"
                    },
                    "queue_position": {
                        "type": "integer",
                        "minimum": 1,
                        "description": "While queued: its place in the queue, 1 is next."
                    },
                    "progress": {
                        "type": "object",
                        "description": "What the scan worker last reported while the job ran, kept after it ends: the stage (unpacking, scanning or saving), each pass (sast, sca, secrets, iac, sbom, cbom, aibom, malicious) as waiting, running, done or skipped (with a reason code), the code pass's files read of files_total, and at, when it was reported. Words and counts only, never code.",
                        "properties": {
                            "stage": {
                                "type": "string",
                                "enum": [
                                    "unpacking",
                                    "scanning",
                                    "saving"
                                ]
                            },
                            "passes": {
                                "type": "array",
                                "items": {
                                    "type": "object",
                                    "properties": {
                                        "name": {
                                            "type": "string"
                                        },
                                        "state": {
                                            "type": "string",
                                            "enum": [
                                                "waiting",
                                                "running",
                                                "done",
                                                "skipped"
                                            ]
                                        },
                                        "reason": {
                                            "type": "string"
                                        }
                                    }
                                }
                            },
                            "files": {
                                "type": "integer"
                            },
                            "files_total": {
                                "type": "integer"
                            },
                            "at": {
                                "type": "string",
                                "format": "date-time"
                            }
                        }
                    }
                },
                "required": [
                    "id",
                    "project",
                    "source",
                    "status",
                    "links_skipped",
                    "code_size",
                    "created_at"
                ]
            }
        },
        "responses": {
            "Refused": {
                "description": "Bad request, unknown token, missing scope or another organisation's id: {error, code, request_id}.",
                "content": {
                    "application/json": {
                        "schema": {
                            "$ref": "#/components/schemas/Error"
                        }
                    }
                }
            },
            "Unauthenticated": {
                "description": "No CI token, or one that is not valid, revoked or expired (a password change of the person who created it revokes it).",
                "content": {
                    "application/json": {
                        "schema": {
                            "$ref": "#/components/schemas/Error"
                        }
                    }
                }
            }
        }
    },
    "paths": {
        "/v1/scan-jobs/upload": {
            "post": {
                "summary": "Upload code for a scan",
                "description": "The body is a .zip or .tar.gz archive of the commit, or a multipart form with a file field. One request to the hosted API carries an archive of about 4 MB, because the body reaches it base64 encoded inside a 6 MB event; stage a larger archive, up to 200 MB, with /v1/scan-jobs/upload/stage. An API without staging (on-premises) takes up to 200 MB here. A repository key that does not exist yet creates the repository with its first scan. The archive's kind is read from its first bytes (the zip or gzip signature), never from Content-Type: only multipart/form-data changes how the body is read (the file field), so any other Content-Type, or none, sends the archive as the body, and a body that is neither a zip nor a gzip-compressed tar is refused (400, \"The file is not a .zip or .tar.gz archive.\"). Limits: 200 MB compressed, 1 GB unpacked, 100,000 files, a compression ratio of 100 and a zip's list of files of at most 24 MB.",
                "parameters": [
                    {
                        "name": "X-Magdox-Repository",
                        "in": "header",
                        "required": true,
                        "schema": {
                            "type": "string",
                            "pattern": "^[a-z0-9][a-z0-9._-]{1,62}[a-z0-9]$"
                        }
                    },
                    {
                        "name": "X-Magdox-Branch",
                        "in": "header",
                        "required": true,
                        "schema": {
                            "type": "string"
                        },
                        "description": "The branch the code comes from, percent-encoded (RFC 3986): MAGDOX decodes it, so a percent sign is sent as %25 and any character outside ASCII as its UTF-8 bytes, such as release%2F100%25 for release/100%."
                    },
                    {
                        "name": "X-Magdox-Commit",
                        "in": "header",
                        "schema": {
                            "type": "string",
                            "pattern": "^[0-9a-f]{7,64}$"
                        }
                    },
                    {
                        "name": "X-Magdox-Pull-Request",
                        "in": "header",
                        "schema": {
                            "type": "string",
                            "pattern": "^[0-9]{1,10}$"
                        },
                        "description": "Send with the next two headers, or none of the three."
                    },
                    {
                        "name": "X-Magdox-Provider",
                        "in": "header",
                        "schema": {
                            "type": "string",
                            "enum": [
                                "github",
                                "gitlab",
                                "bitbucket"
                            ]
                        }
                    },
                    {
                        "name": "X-Magdox-Provider-Repository",
                        "in": "header",
                        "schema": {
                            "type": "string"
                        }
                    }
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/zip": {},
                        "application/gzip": {},
                        "application/octet-stream": {},
                        "multipart/form-data": {
                            "schema": {
                                "type": "object",
                                "required": [
                                    "file"
                                ],
                                "properties": {
                                    "file": {
                                        "type": "string",
                                        "format": "binary"
                                    }
                                }
                            }
                        }
                    },
                    "description": "The archive. Any Content-Type other than multipart/form-data is treated as the archive itself; its kind is sniffed from its bytes."
                },
                "responses": {
                    "201": {
                        "description": "The scan job.",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/ScanJob"
                                }
                            }
                        }
                    },
                    "400": {
                        "$ref": "#/components/responses/Refused"
                    },
                    "413": {
                        "description": "The archive is larger than one request carries (about 4 MB on the hosted API: stage it instead) or larger than 200 MB.",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Error"
                                }
                            }
                        }
                    },
                    "429": {
                        "description": "The daily limit of 200 hosted scans in any 24 hours, or the upload queue; retry after Retry-After seconds.",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Error"
                                }
                            }
                        }
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthenticated"
                    }
                }
            }
        },
        "/v1/scan-jobs/upload/stage": {
            "post": {
                "summary": "Stage an archive over 4 MB",
                "description": "For archives up to 200 MB. Send the same headers as the upload, with a JSON body naming the archive's size in bytes and its SHA-256 in hex. The answer is the job, waiting for its upload, an upload address valid for 5 minutes (expires_in, in seconds) and the headers to send with it. PUT the archive to that address with exactly those headers and no Authorization header: they bind its size and SHA-256. Then start the job with /v1/scan-jobs/start. An API without staging answers 404; send the archive to /v1/scan-jobs/upload there.",
                "parameters": [
                    {
                        "name": "X-Magdox-Repository",
                        "in": "header",
                        "required": true,
                        "schema": {
                            "type": "string",
                            "pattern": "^[a-z0-9][a-z0-9._-]{1,62}[a-z0-9]$"
                        }
                    },
                    {
                        "name": "X-Magdox-Branch",
                        "in": "header",
                        "required": true,
                        "schema": {
                            "type": "string"
                        },
                        "description": "The branch the code comes from, percent-encoded (RFC 3986): MAGDOX decodes it, so a percent sign is sent as %25 and any character outside ASCII as its UTF-8 bytes, such as release%2F100%25 for release/100%."
                    },
                    {
                        "name": "X-Magdox-Commit",
                        "in": "header",
                        "schema": {
                            "type": "string",
                            "pattern": "^[0-9a-f]{7,64}$"
                        }
                    },
                    {
                        "name": "X-Magdox-Pull-Request",
                        "in": "header",
                        "schema": {
                            "type": "string",
                            "pattern": "^[0-9]{1,10}$"
                        },
                        "description": "Send with the next two headers, or none of the three."
                    },
                    {
                        "name": "X-Magdox-Provider",
                        "in": "header",
                        "schema": {
                            "type": "string",
                            "enum": [
                                "github",
                                "gitlab",
                                "bitbucket"
                            ]
                        }
                    },
                    {
                        "name": "X-Magdox-Provider-Repository",
                        "in": "header",
                        "schema": {
                            "type": "string"
                        }
                    }
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "type": "object",
                                "required": [
                                    "size",
                                    "sha256"
                                ],
                                "additionalProperties": false,
                                "properties": {
                                    "size": {
                                        "type": "integer",
                                        "minimum": 1,
                                        "maximum": 209715200,
                                        "description": "The archive's size in bytes."
                                    },
                                    "sha256": {
                                        "type": "string",
                                        "pattern": "^[0-9a-f]{64}$",
                                        "description": "The archive's SHA-256, in hex."
                                    }
                                }
                            }
                        }
                    }
                },
                "responses": {
                    "201": {
                        "description": "The staged job and where to put the archive.",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "type": "object",
                                    "required": [
                                        "job",
                                        "url",
                                        "headers",
                                        "expires_in"
                                    ],
                                    "properties": {
                                        "job": {
                                            "$ref": "#/components/schemas/ScanJob"
                                        },
                                        "url": {
                                            "type": "string",
                                            "description": "Where to PUT the archive, valid for 5 minutes."
                                        },
                                        "headers": {
                                            "type": "object",
                                            "additionalProperties": {
                                                "type": "string"
                                            },
                                            "description": "Send exactly these headers with the PUT."
                                        },
                                        "expires_in": {
                                            "type": "integer",
                                            "description": "Seconds the upload address stays valid: 300."
                                        }
                                    }
                                }
                            }
                        }
                    },
                    "400": {
                        "$ref": "#/components/responses/Refused"
                    },
                    "404": {
                        "description": "Staged uploads are not available here; send the archive to /v1/scan-jobs/upload.",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Error"
                                }
                            }
                        }
                    },
                    "413": {
                        "description": "The archive is larger than 200 MB, whatever size is declared (also one too large for 64 bits).",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Error"
                                }
                            }
                        }
                    },
                    "429": {
                        "description": "The daily limit of 200 hosted scans in any 24 hours.",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Error"
                                }
                            }
                        }
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthenticated"
                    }
                }
            }
        },
        "/v1/scan-jobs/start": {
            "post": {
                "summary": "Start a staged scan job",
                "description": "Starts a staged job once its archive has been stored at the upload address. The job id travels in the body.",
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "$ref": "#/components/schemas/Id"
                            }
                        }
                    }
                },
                "responses": {
                    "200": {
                        "description": "The scan job, queued.",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/ScanJob"
                                }
                            }
                        }
                    },
                    "404": {
                        "$ref": "#/components/responses/Refused"
                    },
                    "409": {
                        "description": "The archive has not arrived yet, or the job has already started.",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Error"
                                }
                            }
                        }
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthenticated"
                    },
                    "400": {
                        "$ref": "#/components/responses/Refused"
                    }
                }
            }
        },
        "/v1/scan-jobs/cancel": {
            "post": {
                "summary": "Cancel a scan job that has not finished",
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "$ref": "#/components/schemas/Id"
                            }
                        }
                    }
                },
                "responses": {
                    "200": {
                        "description": "The scan job.",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/ScanJob"
                                }
                            }
                        }
                    },
                    "404": {
                        "$ref": "#/components/responses/Refused"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthenticated"
                    },
                    "400": {
                        "$ref": "#/components/responses/Refused"
                    }
                }
            }
        },
        "/v1/read/scan-jobs": {
            "post": {
                "summary": "List recent scan jobs",
                "requestBody": {
                    "content": {
                        "application/json": {
                            "schema": {
                                "type": "object",
                                "additionalProperties": false,
                                "properties": {
                                    "project": {
                                        "type": "string",
                                        "description": "Only this repository's jobs."
                                    },
                                    "limit": {
                                        "type": "integer",
                                        "minimum": 1,
                                        "maximum": 100,
                                        "default": 50
                                    },
                                    "offset": {
                                        "type": "integer",
                                        "minimum": 0,
                                        "default": 0
                                    }
                                }
                            }
                        }
                    }
                },
                "responses": {
                    "200": {
                        "description": "Recent scan jobs, newest first, with the total for paging.",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "type": "object",
                                    "properties": {
                                        "jobs": {
                                            "type": "array",
                                            "items": {
                                                "$ref": "#/components/schemas/ScanJob"
                                            }
                                        },
                                        "total": {
                                            "type": "integer"
                                        },
                                        "offset": {
                                            "type": "integer"
                                        }
                                    }
                                }
                            }
                        }
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthenticated"
                    }
                }
            }
        },
        "/v1/read/scan-jobs/item": {
            "post": {
                "summary": "Follow one scan job",
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "$ref": "#/components/schemas/Id"
                            }
                        }
                    }
                },
                "responses": {
                    "200": {
                        "description": "The scan job.",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/ScanJob"
                                }
                            }
                        }
                    },
                    "404": {
                        "$ref": "#/components/responses/Refused"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthenticated"
                    },
                    "400": {
                        "$ref": "#/components/responses/Refused"
                    }
                }
            }
        },
        "/v1/ci/verdict": {
            "post": {
                "summary": "Gate the pipeline on the scan",
                "description": "Ask until state is no longer pending, at most every few seconds. gate pr counts only findings the change introduced, those not open on base_branch, and needs base_branch; release counts every open finding and takes no base_branch. False positives and accepted risks never block. A finished verdict says whether every scanner ran: complete, and when it is false, incomplete (each scanner skipped or run in part, with why) and warning (the same in one plain line for a CI log).",
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "type": "object",
                                "required": [
                                    "job_id",
                                    "gate",
                                    "fail_on"
                                ],
                                "additionalProperties": false,
                                "properties": {
                                    "job_id": {
                                        "type": "string",
                                        "format": "uuid"
                                    },
                                    "gate": {
                                        "type": "string",
                                        "enum": [
                                            "pr",
                                            "release"
                                        ]
                                    },
                                    "fail_on": {
                                        "type": "string",
                                        "enum": [
                                            "critical",
                                            "high",
                                            "medium",
                                            "low",
                                            "info",
                                            "none"
                                        ]
                                    },
                                    "base_branch": {
                                        "type": "string",
                                        "description": "The branch the change merges into: required with gate pr, refused with gate release."
                                    },
                                    "fail_on_skipped": {
                                        "type": "boolean",
                                        "default": false,
                                        "description": "Fail the gate when a scanner was skipped (did not run at all, such as dependencies without a vulnerability database). By default a skipped or partial scanner only warns: the verdict says so in complete, incomplete and warning."
                                    }
                                }
                            }
                        }
                    }
                },
                "responses": {
                    "200": {
                        "description": "job_id, project, status (the scan job's), gate, fail_on, url and state: pending (with stage, where the scan is in a few plain words such as Queued, Unpacking the code, Scanning Code and Infrastructure as code or Saving results), passed, failed or error (with error_code). When passed or failed, also counts by severity, blocking (how many findings failed the gate) and findings: up to 20 of them with rule, severity, surface, path and line, never code. When passed or failed, also complete, incomplete ([{scanner, status: skipped or partial, reason}]) and, when anything is missing, warning. With fail_on_skipped, a skipped scanner makes the state failed with blocking 0."
                    },
                    "404": {
                        "$ref": "#/components/responses/Refused"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthenticated"
                    },
                    "400": {
                        "$ref": "#/components/responses/Refused"
                    }
                }
            }
        },
        "/v1/read/runs": {
            "post": {
                "summary": "Read a finished run",
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "$ref": "#/components/schemas/Id"
                            }
                        }
                    }
                },
                "responses": {
                    "200": {
                        "description": "The run: repository, branch, commit, counts and coverage."
                    },
                    "404": {
                        "$ref": "#/components/responses/Refused"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthenticated"
                    }
                }
            }
        },
        "/v1/read/projects/findings": {
            "post": {
                "summary": "List a repository's findings",
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "type": "object",
                                "required": [
                                    "key"
                                ],
                                "properties": {
                                    "key": {
                                        "type": "string"
                                    }
                                }
                            }
                        }
                    }
                },
                "responses": {
                    "200": {
                        "description": "Findings with what, where and how to fix: title, severity, rule, CWE, file, line, and a short masked excerpt unless your organisation switched excerpts off."
                    },
                    "404": {
                        "$ref": "#/components/responses/Refused"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthenticated"
                    }
                }
            }
        },
        "/v1/runs/artifacts": {
            "post": {
                "summary": "List a run's exports",
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "type": "object",
                                "required": [
                                    "run"
                                ],
                                "properties": {
                                    "run": {
                                        "type": "string"
                                    }
                                },
                                "additionalProperties": false
                            }
                        }
                    }
                },
                "responses": {
                    "200": {
                        "description": "run and artifacts: each with kind, contentType, sha256, size, signature (evidence only) and createdAt. These two names are camelCase, as the console reads them; every other name in this API is snake_case."
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthenticated"
                    }
                }
            }
        },
        "/v1/runs/artifacts/download": {
            "post": {
                "summary": "Download a run's export",
                "description": "Kinds: sbom-cyclonedx, sbom-spdx, cbom, aibom, mlbom, vex, evidence. The answer carries X-Magdox-Artifact-SHA256, and for evidence X-Magdox-Evidence-Signature (Ed25519, base64) with X-Magdox-Evidence-Signature-Domain magdox-evidence-v1.",
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "type": "object",
                                "required": [
                                    "run",
                                    "kind"
                                ],
                                "additionalProperties": false,
                                "properties": {
                                    "run": {
                                        "type": "string"
                                    },
                                    "kind": {
                                        "type": "string",
                                        "enum": [
                                            "sbom-cyclonedx",
                                            "sbom-spdx",
                                            "cbom",
                                            "aibom",
                                            "mlbom",
                                            "vex",
                                            "evidence"
                                        ]
                                    },
                                    "link": {
                                        "type": "boolean",
                                        "description": "Answer {url, sha256, signature} instead of the file. Only where exports are kept in object storage (the hosted service); elsewhere, such as on-premises, the file itself is the answer whatever link says."
                                    }
                                }
                            }
                        }
                    }
                },
                "responses": {
                    "200": {
                        "description": "The file, or {url, sha256, signature} when link was asked for or the file is over 4 MB, on the hosted service."
                    },
                    "404": {
                        "$ref": "#/components/responses/Refused"
                    },
                    "401": {
                        "$ref": "#/components/responses/Unauthenticated"
                    }
                }
            }
        }
    }
}
