{
  "openapi": "3.1.0",
  "info": {
    "title": "artifact-hub",
    "version": "1.0.0",
    "summary": "Publish a file from an agent and get a public URL that renders in a browser.",
    "description": "artifact-hub hosts files generated by AI assistants and serves each one at\n`https://<id>.artifacthub.link`, rendered inline rather than downloaded.\n\nTwo origins, and they are not interchangeable. This API lives entirely on\n`https://api.artifacthub.link`. The artifact bytes live on `https://<id>.artifacthub.link`,\nwhich serves exactly one object at `/`, accepts no credentials of any kind, and\nis not described by this document.\n\nRetries are naturally idempotent and there is no idempotency key: identical\nbytes republished under the same `context-id` return the same URL with no new\nversion.\n\nThe behavioral companion to this file is [llms.txt](https://api.artifacthub.link/llms.txt)."
  },
  "servers": [
    {
      "url": "https://api.artifacthub.link"
    }
  ],
  "security": [
    {
      "bearerAuth": []
    }
  ],
  "tags": [
    {
      "name": "upload",
      "description": "Creating artifacts. Pick by size: under 25 MB of generated text, use `create_artifact`; a file up to 90 MB, `upload_file`; anything larger, `create_upload_ticket`."
    },
    {
      "name": "manage",
      "description": "Listing, editing and retiring what you already published. Every route here is addressed by the artifact's public id and answers 404 — never 403 — for an artifact outside the key's reach, so the id space cannot be enumerated by status code."
    }
  ],
  "paths": {
    "/v1/{filename}": {
      "put": {
        "operationId": "upload_file",
        "tags": [
          "upload"
        ],
        "summary": "Upload a file's raw bytes and get a public URL back as one line of plain text. Use for files up to 90 MB. For larger files use create_upload_ticket; to publish generated text you do not have on disk, use create_artifact.",
        "description": "The body is the file, sent as-is with no wrapper.\n\n**The path prefix `/v1/` is required.** A `PUT` at the apex is answered by the\nstatic-asset layer with a bare 405 and never reaches this API.\n\n**Response format is negotiated by `Accept` only.** The default is\n`text/plain`: the URL and exactly one newline, so `URL=$(curl ...)` yields a\nusable variable with no parsing. JSON comes back only when `Accept` names\n`application/json` outright — `*/*`, which curl sends by default, does not\ncount. There is no `?format=` parameter.\n\nErrors on this route follow the same rule: `text/plain` with the message and\na newline unless JSON was requested by name.\n\n**Send `Content-Length`.** A body with no declared length cannot be streamed\nto storage, so it is buffered in the isolate and capped at\n25 MB instead of 90 MB.\n\n**The `Content-Type` you send is stored for forensics and never trusted for\nserving.** The served type is recomputed from the bytes and the filename\nagainst a fixed allowlist.",
        "parameters": [
          {
            "name": "filename",
            "in": "path",
            "required": true,
            "description": "The file's name, including its extension. Metadata only — it never appears in the public URL — but it is what decides the served content type. With `curl -T` you may write the URL as `/v1/` and let curl append the local basename, but ONLY when the URL has no query string; with any query parameter curl appends nothing and the request is a 400.",
            "schema": {
              "type": "string"
            },
            "example": "report.html"
          },
          {
            "name": "context-id",
            "in": "query",
            "required": false,
            "description": "Groups uploads. The same context id plus the same item means the same URL and a new version instead of a second link. Hyphenated here; `context_id` in a JSON body. Nothing is inferred server-side, so without this every upload creates a new artifact.",
            "schema": {
              "type": "string",
              "maxLength": 128
            },
            "example": "sess_01K1XQ8Z4Y7N3M2P6R9T5V0BWC"
          },
          {
            "name": "expires",
            "in": "query",
            "required": false,
            "description": "Retention window. Anything else is a 400.",
            "schema": {
              "type": "string",
              "enum": [
                "12h",
                "7d",
                "30d",
                "never"
              ],
              "default": "30d"
            }
          },
          {
            "name": "new-link",
            "in": "query",
            "required": false,
            "description": "Force a new artifact even when the context matches. `1`, `true` or `yes` for on.",
            "schema": {
              "type": "string",
              "enum": [
                "1",
                "0",
                "true",
                "false",
                "yes",
                "no"
              ]
            }
          },
          {
            "name": "path",
            "in": "query",
            "required": false,
            "description": "Folder to file the artifact under, from the root, creating what does not exist (`mkdir -p`). Names fold on case and accents, and the first spelling wins. NOT hyphenated and not snake_case — `path` is one word, so the query string and the JSON body spell it identically. IGNORED on a republication: the folder is set when an artifact is born, and a new version does not move it. Use `PATCH /v1/artifacts/{id}` with `folder_id` to refile one.",
            "schema": {
              "type": "string",
              "maxLength": 1024
            },
            "example": "reports/2026"
          },
          {
            "name": "update",
            "in": "query",
            "required": false,
            "description": "Force a new version of one specific artifact, by its id.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "title",
            "in": "query",
            "required": false,
            "description": "Human title for the link preview card. Defaults to the filename.",
            "schema": {
              "type": "string",
              "maxLength": 200
            }
          },
          {
            "name": "vanity",
            "in": "query",
            "required": false,
            "deprecated": true,
            "description": "NOT IMPLEMENTED. Accepted as a known parameter and always rejected with 400, rather than silently dropped — a dropped vanity label would hand back a generated id under a 201 and the caller would share a link that is not the one they asked for. Omit it.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "description": "The file's bytes, unwrapped.",
          "content": {
            "application/octet-stream": {
              "schema": {
                "type": "string",
                "format": "binary"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "A new version of an existing artifact, or a deduplicated republish. The URL is unchanged from the previous call.",
            "headers": {
              "Location": {
                "description": "The public URL.",
                "schema": {
                  "type": "string",
                  "format": "uri"
                }
              },
              "Cache-Control": {
                "description": "Always `no-store`. The URL is a secret in the shape of a link; nothing in between may keep a copy.",
                "schema": {
                  "type": "string"
                }
              },
              "X-Artifact-Id": {
                "schema": {
                  "type": "string"
                }
              },
              "X-Artifact-Url": {
                "schema": {
                  "type": "string",
                  "format": "uri"
                }
              },
              "X-Artifact-Version": {
                "schema": {
                  "type": "integer"
                }
              },
              "X-Artifact-Content-Sha256": {
                "description": "Absent if the post-write read-back failed.",
                "schema": {
                  "type": "string"
                }
              },
              "X-Artifact-Expires-At": {
                "description": "Absent — not the string `never` — when the artifact does not expire.",
                "schema": {
                  "type": "string",
                  "format": "date-time"
                }
              },
              "X-Artifact-Deduplicated": {
                "description": "`true` when identical bytes already existed in this context, so no new version was created.",
                "schema": {
                  "type": "boolean"
                }
              },
              "X-Request-Id": {
                "description": "On every response, success and failure alike.",
                "schema": {
                  "type": "string"
                }
              }
            },
            "content": {
              "text/plain": {
                "schema": {
                  "type": "string"
                },
                "example": "https://quiet-harbor-3k9m2xq7wp4v8ntb5rjz6yfd0s.artifacthub.link/\n"
              },
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Artifact"
                },
                "example": {
                  "id": "quiet-harbor-3k9m2xq7wp4v8ntb5rjz6yfd0s",
                  "url": "https://quiet-harbor-3k9m2xq7wp4v8ntb5rjz6yfd0s.artifacthub.link/",
                  "version": 2,
                  "filename": "report.html",
                  "content_type": "text/html; charset=utf-8",
                  "size": 48219,
                  "sha256": "9c1185a5c5e9fc54612808977ee8f548b2258d31a3b1f2c0e6d4a7b8c9d0e1f2",
                  "deduplicated": false,
                  "context_id": null,
                  "expires_at": "2026-09-03T10:12:33Z",
                  "created_at": "2026-08-04T10:12:33Z"
                }
              }
            }
          },
          "201": {
            "description": "A new artifact was created.",
            "headers": {
              "Location": {
                "description": "The public URL.",
                "schema": {
                  "type": "string",
                  "format": "uri"
                }
              },
              "Cache-Control": {
                "description": "Always `no-store`. The URL is a secret in the shape of a link; nothing in between may keep a copy.",
                "schema": {
                  "type": "string"
                }
              },
              "X-Artifact-Id": {
                "schema": {
                  "type": "string"
                }
              },
              "X-Artifact-Url": {
                "schema": {
                  "type": "string",
                  "format": "uri"
                }
              },
              "X-Artifact-Version": {
                "schema": {
                  "type": "integer"
                }
              },
              "X-Artifact-Content-Sha256": {
                "description": "Absent if the post-write read-back failed.",
                "schema": {
                  "type": "string"
                }
              },
              "X-Artifact-Expires-At": {
                "description": "Absent — not the string `never` — when the artifact does not expire.",
                "schema": {
                  "type": "string",
                  "format": "date-time"
                }
              },
              "X-Artifact-Deduplicated": {
                "description": "`true` when identical bytes already existed in this context, so no new version was created.",
                "schema": {
                  "type": "boolean"
                }
              },
              "X-Request-Id": {
                "description": "On every response, success and failure alike.",
                "schema": {
                  "type": "string"
                }
              }
            },
            "content": {
              "text/plain": {
                "schema": {
                  "type": "string"
                },
                "example": "https://quiet-harbor-3k9m2xq7wp4v8ntb5rjz6yfd0s.artifacthub.link/\n"
              },
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Artifact"
                },
                "example": {
                  "id": "quiet-harbor-3k9m2xq7wp4v8ntb5rjz6yfd0s",
                  "url": "https://quiet-harbor-3k9m2xq7wp4v8ntb5rjz6yfd0s.artifacthub.link/",
                  "version": 1,
                  "filename": "report.html",
                  "content_type": "text/html; charset=utf-8",
                  "size": 48219,
                  "sha256": "9c1185a5c5e9fc54612808977ee8f548b2258d31a3b1f2c0e6d4a7b8c9d0e1f2",
                  "deduplicated": false,
                  "context_id": null,
                  "expires_at": "2026-09-03T10:12:33Z",
                  "created_at": "2026-08-04T10:12:33Z"
                }
              }
            }
          },
          "400": {
            "description": "No filename in the path, an unknown query parameter, a bad `expires`, or `vanity`. Unknown parameters are rejected, never ignored.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                },
                "example": {
                  "error": {
                    "type": "invalid_request",
                    "message": "Unknown query parameter: context_id. This endpoint accepts: context-id, expires, new-link, path, title, update, vanity. Note that a PUT takes hyphenated query parameters (context-id) while the JSON body takes snake_case (context_id). Fix the name and retry.",
                    "request_id": "req_01K1XQ8Z4Y7N3M2P6R9T5V0BWH",
                    "docs": "https://api.artifacthub.link/docs/errors#invalid_request"
                  }
                }
              }
            }
          },
          "401": {
            "description": "No credential, or one that is malformed, unknown, revoked or expired. The `type` distinguishes them; only `missing_credentials` is fixable by the agent.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                },
                "example": {
                  "error": {
                    "type": "missing_credentials",
                    "message": "No token was sent. Set the ARTIFACT_HUB_TOKEN environment variable and send it as Authorization: Bearer $ARTIFACT_HUB_TOKEN. Never place a token on a command line.",
                    "request_id": "req_01K1XQ8Z4Y7N3M2P6R9T5V0BWC",
                    "docs": "https://api.artifacthub.link/docs/errors#missing_credentials"
                  }
                }
              }
            }
          },
          "403": {
            "description": "The key is valid but not permitted. Only the account owner can change this; do not retry and do not try another endpoint.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                },
                "example": {
                  "error": {
                    "type": "insufficient_scope",
                    "message": "This key does not have the artifacts:write scope. The account owner can add it to key 7Qm2xR4a. Do not retry this call.",
                    "request_id": "req_01K1XQ8Z4Y7N3M2P6R9T5V0BWD",
                    "docs": "https://api.artifacthub.link/docs/errors#insufficient_scope"
                  }
                }
              }
            }
          },
          "413": {
            "description": "Body over 90 MB. Switch to create_upload_ticket; do not retry here.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                },
                "example": {
                  "error": {
                    "type": "payload_too_large",
                    "message": "File is 143 MB; the direct upload limit is 90 MB. Request a presigned URL with POST /v1/uploads and PUT the file there — that path accepts up to 5 GB. Do not retry this call against the same endpoint.",
                    "request_id": "req_01K1XQ8Z4Y7N3M2P6R9T5V0BWJ",
                    "docs": "https://api.artifacthub.link/docs/errors#payload_too_large"
                  }
                }
              }
            }
          },
          "429": {
            "description": "Rate limit (`rate_limited`, with `Retry-After`) or quota (`quota_exceeded`, deliberately **without** `Retry-After`, because waiting does not help).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                },
                "example": {
                  "error": {
                    "type": "rate_limited",
                    "message": "Too many requests. Wait 30 seconds and retry once. Do not retry in a loop.",
                    "request_id": "req_01K1XQ8Z4Y7N3M2P6R9T5V0BWE",
                    "docs": "https://api.artifacthub.link/docs/errors#rate_limited",
                    "retry_after": 30
                  }
                }
              }
            }
          },
          "500": {
            "description": "Ours. Retry once, then relay the `request_id` and stop.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                },
                "example": {
                  "error": {
                    "type": "internal_error",
                    "message": "An unexpected error occurred. Retry once; if it fails again, quote the request_id.",
                    "request_id": "req_01K1XQ8Z4Y7N3M2P6R9T5V0BWF",
                    "docs": "https://api.artifacthub.link/docs/errors#internal_error"
                  }
                }
              }
            }
          },
          "503": {
            "description": "A dependency is unavailable, **or** a capability this deployment was never configured with — the message says which, and names the missing configuration by variable.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                },
                "example": {
                  "error": {
                    "type": "service_unavailable",
                    "message": "Storage is temporarily unavailable. Retry after the number of seconds in Retry-After, at most twice.",
                    "request_id": "req_01K1XQ8Z4Y7N3M2P6R9T5V0BWG",
                    "docs": "https://api.artifacthub.link/docs/errors#service_unavailable",
                    "retry_after": 5
                  }
                }
              }
            }
          }
        }
      }
    },
    "/v1/artifacts": {
      "get": {
        "operationId": "list_artifacts",
        "tags": [
          "manage"
        ],
        "summary": "List the artifacts this key can reach, newest first, optionally filtered by folder (folder_id), searched by title or filename (q) and reordered (sort). Use it to find an id before calling get_artifact, update_artifact or delete_artifact — do not guess an id, and do not reconstruct one from a URL you were given.",
        "description": "Keyset pagination, not offset: pass the `next_cursor` from the previous\nresponse back as `cursor`. `next_cursor` is `null` on the last page.\n\nWhat this returns depends on the key. A key issued with `resource_mode: own`\nsees only the artifacts its own lineage created; one with `all` sees the\naccount's. There is no parameter that widens it.\n\n`folder_id`, `q` and `sort` narrow and order the SAME listing, across the\nwhole account rather than one level: this is `find`, and\n`list_folder_children` is `ls`. They combine, and `q` searches the title and\nthe current version's filename together.\n\n`context_id` and `status` are documented filters that are **not implemented**\nand are rejected with a 400 rather than ignored — an ignored filter returns a\nsuperset the caller believes is filtered, and acts on it.",
        "parameters": [
          {
            "name": "cursor",
            "in": "query",
            "required": false,
            "description": "The previous response's `next_cursor`, verbatim.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "folder_id",
            "in": "query",
            "required": false,
            "description": "List one folder only. Takes a folder id, or the literal `root` for the top level; OMIT it for every folder — those are three states, so `folder_id=root` and an absent `folder_id` are different questions and an empty `folder_id=` is a 400. Not recursive: an artifact in a subfolder of this one is not returned. Snake_case here, like every parameter on this route, unlike the hyphenated ones on `PUT /v1/{filename}`.",
            "schema": {
              "type": "string"
            },
            "example": "root"
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 100
            }
          },
          {
            "name": "q",
            "in": "query",
            "required": false,
            "description": "Substring search over the title and the current version's filename. Both sides are folded on case and accents the same way folder names are, so `RELAT`, `relatorios` and `Relatórios` all find `Relatórios`. Trimmed; an empty or whitespace-only term filters nothing rather than matching nothing. Over 200 characters is a 400.",
            "schema": {
              "type": "string",
              "maxLength": 200
            },
            "example": "invoice"
          },
          {
            "name": "sort",
            "in": "query",
            "required": false,
            "description": "Ordering. `created` (default), `updated` and `published` are newest-first; `name` is A→Z by the artifact's TITLE, NOT by the filename of its current version. A `next_cursor` is a position in ONE ordering — change `sort` on a later page and the call is a 400, so start the listing again instead.",
            "schema": {
              "type": "string",
              "enum": [
                "created",
                "name",
                "published",
                "updated"
              ],
              "default": "created"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "A page of artifacts.",
            "headers": {
              "Cache-Control": {
                "description": "Always `no-store`. These bodies name public URLs, which are secrets in the shape of a link.",
                "schema": {
                  "type": "string"
                }
              },
              "X-Request-Id": {
                "description": "On every response, success and failure alike.",
                "schema": {
                  "type": "string"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ArtifactPage"
                },
                "example": {
                  "data": [
                    {
                      "id": "quiet-harbor-3k9m2xq7wp4v8ntb5rjz6yfd0s",
                      "url": "https://quiet-harbor-3k9m2xq7wp4v8ntb5rjz6yfd0s.artifacthub.link/",
                      "title": "Q3 summary",
                      "version": 2,
                      "filename": "report.html",
                      "content_type": "text/html; charset=utf-8",
                      "size": 48219,
                      "visibility": "public",
                      "comments_enabled": false,
                      "created_at": "2026-08-04T10:12:33Z",
                      "updated_at": "2026-08-04T10:12:33Z",
                      "expires_at": "2026-09-03T10:12:33Z",
                      "deleted_at": null
                    }
                  ],
                  "next_cursor": null
                }
              }
            }
          },
          "400": {
            "description": "An unknown or unimplemented query parameter. Rejected, never ignored.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                },
                "example": {
                  "error": {
                    "type": "invalid_request",
                    "message": "Unknown query parameter: status. This endpoint accepts: cursor, folder_id, limit, q, sort. The context_id and status filters are documented but not implemented, and are refused rather than ignored — an ignored filter returns more than you asked for. Note also that query parameters here are snake_case while PUT /v1/<filename> takes hyphenated ones.",
                    "request_id": "req_01K1XQ8Z4Y7N3M2P6R9T5V0BWV",
                    "docs": "https://api.artifacthub.link/docs/errors#invalid_request"
                  }
                }
              }
            }
          },
          "401": {
            "description": "No credential, or one that is malformed, unknown, revoked or expired. The `type` distinguishes them; only `missing_credentials` is fixable by the agent.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                },
                "example": {
                  "error": {
                    "type": "missing_credentials",
                    "message": "No token was sent. Set the ARTIFACT_HUB_TOKEN environment variable and send it as Authorization: Bearer $ARTIFACT_HUB_TOKEN. Never place a token on a command line.",
                    "request_id": "req_01K1XQ8Z4Y7N3M2P6R9T5V0BWC",
                    "docs": "https://api.artifacthub.link/docs/errors#missing_credentials"
                  }
                }
              }
            }
          },
          "403": {
            "description": "The key is valid but not permitted. Only the account owner can change this; do not retry and do not try another endpoint.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                },
                "example": {
                  "error": {
                    "type": "insufficient_scope",
                    "message": "This key does not have the artifacts:write scope. The account owner can add it to key 7Qm2xR4a. Do not retry this call.",
                    "request_id": "req_01K1XQ8Z4Y7N3M2P6R9T5V0BWD",
                    "docs": "https://api.artifacthub.link/docs/errors#insufficient_scope"
                  }
                }
              }
            }
          },
          "429": {
            "description": "Rate limit (`rate_limited`, with `Retry-After`) or quota (`quota_exceeded`, deliberately **without** `Retry-After`, because waiting does not help).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                },
                "example": {
                  "error": {
                    "type": "rate_limited",
                    "message": "Too many requests. Wait 30 seconds and retry once. Do not retry in a loop.",
                    "request_id": "req_01K1XQ8Z4Y7N3M2P6R9T5V0BWE",
                    "docs": "https://api.artifacthub.link/docs/errors#rate_limited",
                    "retry_after": 30
                  }
                }
              }
            }
          },
          "500": {
            "description": "Ours. Retry once, then relay the `request_id` and stop.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                },
                "example": {
                  "error": {
                    "type": "internal_error",
                    "message": "An unexpected error occurred. Retry once; if it fails again, quote the request_id.",
                    "request_id": "req_01K1XQ8Z4Y7N3M2P6R9T5V0BWF",
                    "docs": "https://api.artifacthub.link/docs/errors#internal_error"
                  }
                }
              }
            }
          },
          "503": {
            "description": "A dependency is unavailable, **or** a capability this deployment was never configured with — the message says which, and names the missing configuration by variable.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                },
                "example": {
                  "error": {
                    "type": "service_unavailable",
                    "message": "Storage is temporarily unavailable. Retry after the number of seconds in Retry-After, at most twice.",
                    "request_id": "req_01K1XQ8Z4Y7N3M2P6R9T5V0BWG",
                    "docs": "https://api.artifacthub.link/docs/errors#service_unavailable",
                    "retry_after": 5
                  }
                }
              }
            }
          }
        }
      },
      "post": {
        "operationId": "create_artifact",
        "tags": [
          "upload"
        ],
        "summary": "Publish content you have in memory — a generated HTML page, a report, a JSON document — without writing it to disk first. Use when the content is a string; use upload_file for a file on disk, and create_upload_ticket above 90 MB.",
        "description": "Accepts `application/json` with the content as a string, or\n`multipart/form-data` with the bytes in a part named `file`.\n\n**Field names are snake_case here** (`context_id`, `new_link`) while the\nquery string of `upload_file` uses hyphens (`context-id`, `new-link`). Both\nendpoints reject the other spelling with a 400 rather than ignoring it.\n\nThe JSON path is capped at 25 MB **decoded** — the body is parsed whole,\nin memory, and base64 costs a further 4/3 on the wire. The multipart path\nreaches 90 MB but buffers the part, so a large upload is safer as\n`upload_file`, which streams.\n\nUnlike `upload_file`, this route answers JSON by default.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CreateArtifactRequest"
              },
              "example": {
                "filename": "summary.html",
                "content": "<!doctype html><h1>Q3 summary</h1>",
                "content_type": "text/html; charset=utf-8",
                "context_id": "sess_01K1XQ8Z4Y7N3M2P6R9T5V0BWC",
                "expires": "7d"
              }
            },
            "multipart/form-data": {
              "schema": {
                "type": "object",
                "required": [
                  "file"
                ],
                "additionalProperties": false,
                "properties": {
                  "file": {
                    "type": "string",
                    "format": "binary",
                    "description": "The bytes. The part must be named exactly `file`."
                  },
                  "filename": {
                    "type": "string",
                    "description": "Optional; taken from the file part's own filename when absent."
                  },
                  "content_type": {
                    "type": "string"
                  },
                  "context_id": {
                    "type": "string",
                    "maxLength": 128
                  },
                  "expires": {
                    "type": "string",
                    "enum": [
                      "12h",
                      "7d",
                      "30d",
                      "never"
                    ]
                  },
                  "new_link": {
                    "type": "string"
                  },
                  "path": {
                    "type": "string",
                    "maxLength": 1024
                  },
                  "title": {
                    "type": "string",
                    "maxLength": 200
                  },
                  "update": {
                    "type": "string"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "A new version of an existing artifact, or a deduplicated republish.",
            "headers": {
              "Location": {
                "description": "The public URL.",
                "schema": {
                  "type": "string",
                  "format": "uri"
                }
              },
              "Cache-Control": {
                "description": "Always `no-store`. The URL is a secret in the shape of a link; nothing in between may keep a copy.",
                "schema": {
                  "type": "string"
                }
              },
              "X-Artifact-Id": {
                "schema": {
                  "type": "string"
                }
              },
              "X-Artifact-Url": {
                "schema": {
                  "type": "string",
                  "format": "uri"
                }
              },
              "X-Artifact-Version": {
                "schema": {
                  "type": "integer"
                }
              },
              "X-Artifact-Content-Sha256": {
                "description": "Absent if the post-write read-back failed.",
                "schema": {
                  "type": "string"
                }
              },
              "X-Artifact-Expires-At": {
                "description": "Absent — not the string `never` — when the artifact does not expire.",
                "schema": {
                  "type": "string",
                  "format": "date-time"
                }
              },
              "X-Artifact-Deduplicated": {
                "description": "`true` when identical bytes already existed in this context, so no new version was created.",
                "schema": {
                  "type": "boolean"
                }
              },
              "X-Request-Id": {
                "description": "On every response, success and failure alike.",
                "schema": {
                  "type": "string"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Artifact"
                },
                "example": {
                  "id": "quiet-harbor-3k9m2xq7wp4v8ntb5rjz6yfd0s",
                  "url": "https://quiet-harbor-3k9m2xq7wp4v8ntb5rjz6yfd0s.artifacthub.link/",
                  "version": 2,
                  "filename": "summary.html",
                  "content_type": "text/html; charset=utf-8",
                  "size": 48219,
                  "sha256": "9c1185a5c5e9fc54612808977ee8f548b2258d31a3b1f2c0e6d4a7b8c9d0e1f2",
                  "deduplicated": false,
                  "context_id": null,
                  "expires_at": "2026-09-03T10:12:33Z",
                  "created_at": "2026-08-04T10:12:33Z"
                }
              }
            }
          },
          "201": {
            "description": "A new artifact was created.",
            "headers": {
              "Location": {
                "description": "The public URL.",
                "schema": {
                  "type": "string",
                  "format": "uri"
                }
              },
              "Cache-Control": {
                "description": "Always `no-store`. The URL is a secret in the shape of a link; nothing in between may keep a copy.",
                "schema": {
                  "type": "string"
                }
              },
              "X-Artifact-Id": {
                "schema": {
                  "type": "string"
                }
              },
              "X-Artifact-Url": {
                "schema": {
                  "type": "string",
                  "format": "uri"
                }
              },
              "X-Artifact-Version": {
                "schema": {
                  "type": "integer"
                }
              },
              "X-Artifact-Content-Sha256": {
                "description": "Absent if the post-write read-back failed.",
                "schema": {
                  "type": "string"
                }
              },
              "X-Artifact-Expires-At": {
                "description": "Absent — not the string `never` — when the artifact does not expire.",
                "schema": {
                  "type": "string",
                  "format": "date-time"
                }
              },
              "X-Artifact-Deduplicated": {
                "description": "`true` when identical bytes already existed in this context, so no new version was created.",
                "schema": {
                  "type": "boolean"
                }
              },
              "X-Request-Id": {
                "description": "On every response, success and failure alike.",
                "schema": {
                  "type": "string"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Artifact"
                },
                "example": {
                  "id": "quiet-harbor-3k9m2xq7wp4v8ntb5rjz6yfd0s",
                  "url": "https://quiet-harbor-3k9m2xq7wp4v8ntb5rjz6yfd0s.artifacthub.link/",
                  "version": 1,
                  "filename": "summary.html",
                  "content_type": "text/html; charset=utf-8",
                  "size": 48219,
                  "sha256": "9c1185a5c5e9fc54612808977ee8f548b2258d31a3b1f2c0e6d4a7b8c9d0e1f2",
                  "deduplicated": false,
                  "context_id": null,
                  "expires_at": "2026-09-03T10:12:33Z",
                  "created_at": "2026-08-04T10:12:33Z"
                }
              }
            }
          },
          "400": {
            "description": "Malformed JSON, an unknown field, no `filename`, both `content` and `content_base64`, neither of them, or `vanity`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                },
                "example": {
                  "error": {
                    "type": "invalid_request",
                    "message": "One of content (a UTF-8 string) or content_base64 is required.",
                    "request_id": "req_01K1XQ8Z4Y7N3M2P6R9T5V0BWK",
                    "docs": "https://api.artifacthub.link/docs/errors#invalid_request"
                  }
                }
              }
            }
          },
          "401": {
            "description": "No credential, or one that is malformed, unknown, revoked or expired. The `type` distinguishes them; only `missing_credentials` is fixable by the agent.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                },
                "example": {
                  "error": {
                    "type": "missing_credentials",
                    "message": "No token was sent. Set the ARTIFACT_HUB_TOKEN environment variable and send it as Authorization: Bearer $ARTIFACT_HUB_TOKEN. Never place a token on a command line.",
                    "request_id": "req_01K1XQ8Z4Y7N3M2P6R9T5V0BWC",
                    "docs": "https://api.artifacthub.link/docs/errors#missing_credentials"
                  }
                }
              }
            }
          },
          "403": {
            "description": "The key is valid but not permitted. Only the account owner can change this; do not retry and do not try another endpoint.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                },
                "example": {
                  "error": {
                    "type": "insufficient_scope",
                    "message": "This key does not have the artifacts:write scope. The account owner can add it to key 7Qm2xR4a. Do not retry this call.",
                    "request_id": "req_01K1XQ8Z4Y7N3M2P6R9T5V0BWD",
                    "docs": "https://api.artifacthub.link/docs/errors#insufficient_scope"
                  }
                }
              }
            }
          },
          "413": {
            "description": "Decoded content over 25 MB.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                },
                "example": {
                  "error": {
                    "type": "payload_too_large",
                    "message": "Inline content is 40 MB; this route accepts 25 MB decoded, because a JSON body is parsed entirely in memory. Send the bytes with PUT /<filename>, which streams and accepts 90 MB, or request a presigned URL with POST /v1/uploads for anything larger.",
                    "request_id": "req_01K1XQ8Z4Y7N3M2P6R9T5V0BWT",
                    "docs": "https://api.artifacthub.link/docs/errors#payload_too_large"
                  }
                }
              }
            }
          },
          "415": {
            "description": "A `Content-Type` other than `application/json` or `multipart/form-data`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                },
                "example": {
                  "error": {
                    "type": "unsupported_media_type",
                    "message": "/v1/artifacts does not accept text/plain. Send one of: application/json, multipart/form-data. Do not retry this call with the same Content-Type.",
                    "request_id": "req_01K1XQ8Z4Y7N3M2P6R9T5V0BWM",
                    "docs": "https://api.artifacthub.link/docs/errors#unsupported_media_type"
                  }
                }
              }
            }
          },
          "429": {
            "description": "Rate limit (`rate_limited`, with `Retry-After`) or quota (`quota_exceeded`, deliberately **without** `Retry-After`, because waiting does not help).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                },
                "example": {
                  "error": {
                    "type": "rate_limited",
                    "message": "Too many requests. Wait 30 seconds and retry once. Do not retry in a loop.",
                    "request_id": "req_01K1XQ8Z4Y7N3M2P6R9T5V0BWE",
                    "docs": "https://api.artifacthub.link/docs/errors#rate_limited",
                    "retry_after": 30
                  }
                }
              }
            }
          },
          "500": {
            "description": "Ours. Retry once, then relay the `request_id` and stop.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                },
                "example": {
                  "error": {
                    "type": "internal_error",
                    "message": "An unexpected error occurred. Retry once; if it fails again, quote the request_id.",
                    "request_id": "req_01K1XQ8Z4Y7N3M2P6R9T5V0BWF",
                    "docs": "https://api.artifacthub.link/docs/errors#internal_error"
                  }
                }
              }
            }
          },
          "503": {
            "description": "A dependency is unavailable, **or** a capability this deployment was never configured with — the message says which, and names the missing configuration by variable.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                },
                "example": {
                  "error": {
                    "type": "service_unavailable",
                    "message": "Storage is temporarily unavailable. Retry after the number of seconds in Retry-After, at most twice.",
                    "request_id": "req_01K1XQ8Z4Y7N3M2P6R9T5V0BWG",
                    "docs": "https://api.artifacthub.link/docs/errors#service_unavailable",
                    "retry_after": 5
                  }
                }
              }
            }
          }
        }
      }
    },
    "/v1/uploads": {
      "post": {
        "operationId": "create_upload_ticket",
        "tags": [
          "upload"
        ],
        "summary": "Get a short-lived presigned URL for a file too big to send directly — over 90 MB and up to 5 GB. Three steps, not one; for anything smaller use upload_file, which is a single call.",
        "description": "Returns a ticket. Then: `PUT` the bytes to `ticket.url` with\n`ticket.headers` copied **verbatim** and no `Authorization` header, and\nfinally `POST` to `ticket.complete.url` with your bearer key to register the\nobject and get the artifact back.\n\n**`ticket.url` is on a different host from this API.** Presigning works only\nagainst the storage service's own endpoint. Use the URL exactly as returned;\nit cannot be rebuilt from a hostname you already know.\n\n**`ticket.headers` are covered by the signature.** Adding, removing or\naltering one produces a bare 403 with no body from the storage service, which\nreads like an expired ticket and is not.\n\n**`max_bytes` is advisory.** A presigned `PUT` cannot enforce a size cap. The\nlimit is applied at `complete_upload`, which rejects an oversized object and\ndeletes it. `size` is required here precisely so an impossible upload is\nrefused before a URL is issued rather than after the transfer.\n\n**Send `sha256` whenever you can compute it** (`shasum -a 256 <file>`). With\nthe digest known up front the ticket points at the final content-addressed\nkey and `complete_upload` is a metadata write; without it the object lands in\nstaging and has to be copied server-side afterwards.\n\nA **503** naming unset variables means presigned uploads are not configured\non this deployment. That is a designed state, not an outage: relay the\nmessage and fall back to `upload_file` for anything under\n90 MB.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CreateUploadTicketRequest"
              },
              "example": {
                "filename": "demo.mp4",
                "content_type": "video/mp4",
                "size": 2147483648,
                "sha256": "c2f8a1d4e7b0396258af1c3d5e7092b4a6c8d0e2f4061738495a6b7c8d9e0f12",
                "context_id": "sess_01K1XQ8Z4Y7N3M2P6R9T5V0BWC"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "A ticket. It expires in 15 minutes.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/UploadTicket"
                },
                "example": {
                  "upload_id": "up_01K1XQ9F3B2M7T5R0N6WYD8VJK",
                  "method": "PUT",
                  "url": "https://a1b2c3d4e5f60718293a4b5c6d7e8f90.r2.cloudflarestorage.com/artifact-hub/blob/c2f8a1d4?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-SignedHeaders=content-type%3Bhost",
                  "headers": {
                    "content-type": "video/mp4"
                  },
                  "expires_at": "2026-08-04T10:27:33Z",
                  "max_bytes": 5000000000,
                  "complete": {
                    "method": "POST",
                    "url": "https://api.artifacthub.link/v1/uploads/up_01K1XQ9F3B2M7T5R0N6WYD8VJK/complete"
                  }
                }
              }
            }
          },
          "400": {
            "description": "No `filename`, no `size`, a non-JSON body, an unknown field, or `vanity`. Field names are snake_case here.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                },
                "example": {
                  "error": {
                    "type": "invalid_request",
                    "message": "size is required and must be a number: the file's exact length in bytes. It is what decides whether a ticket can be issued at all, so a ticket cannot be minted without it.",
                    "request_id": "req_01K1XQ8Z4Y7N3M2P6R9T5V0BWN",
                    "docs": "https://api.artifacthub.link/docs/errors#invalid_request"
                  }
                }
              }
            }
          },
          "401": {
            "description": "No credential, or one that is malformed, unknown, revoked or expired. The `type` distinguishes them; only `missing_credentials` is fixable by the agent.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                },
                "example": {
                  "error": {
                    "type": "missing_credentials",
                    "message": "No token was sent. Set the ARTIFACT_HUB_TOKEN environment variable and send it as Authorization: Bearer $ARTIFACT_HUB_TOKEN. Never place a token on a command line.",
                    "request_id": "req_01K1XQ8Z4Y7N3M2P6R9T5V0BWC",
                    "docs": "https://api.artifacthub.link/docs/errors#missing_credentials"
                  }
                }
              }
            }
          },
          "403": {
            "description": "The key is valid but not permitted. Only the account owner can change this; do not retry and do not try another endpoint.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                },
                "example": {
                  "error": {
                    "type": "insufficient_scope",
                    "message": "This key does not have the artifacts:write scope. The account owner can add it to key 7Qm2xR4a. Do not retry this call.",
                    "request_id": "req_01K1XQ8Z4Y7N3M2P6R9T5V0BWD",
                    "docs": "https://api.artifacthub.link/docs/errors#insufficient_scope"
                  }
                }
              }
            }
          },
          "413": {
            "description": "A declared `size` over 5 GB. Nothing larger can be stored; split the file.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                },
                "example": {
                  "error": {
                    "type": "payload_too_large",
                    "message": "size is 6000000000 bytes; the maximum for a single object is 5000000000. Multipart upload is not available, so a larger file has to be split.",
                    "request_id": "req_01K1XQ8Z4Y7N3M2P6R9T5V0BWP",
                    "docs": "https://api.artifacthub.link/docs/errors#payload_too_large"
                  }
                }
              }
            }
          },
          "429": {
            "description": "Rate limit (`rate_limited`, with `Retry-After`) or quota (`quota_exceeded`, deliberately **without** `Retry-After`, because waiting does not help).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                },
                "example": {
                  "error": {
                    "type": "rate_limited",
                    "message": "Too many requests. Wait 30 seconds and retry once. Do not retry in a loop.",
                    "request_id": "req_01K1XQ8Z4Y7N3M2P6R9T5V0BWE",
                    "docs": "https://api.artifacthub.link/docs/errors#rate_limited",
                    "retry_after": 30
                  }
                }
              }
            }
          },
          "500": {
            "description": "Ours. Retry once, then relay the `request_id` and stop.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                },
                "example": {
                  "error": {
                    "type": "internal_error",
                    "message": "An unexpected error occurred. Retry once; if it fails again, quote the request_id.",
                    "request_id": "req_01K1XQ8Z4Y7N3M2P6R9T5V0BWF",
                    "docs": "https://api.artifacthub.link/docs/errors#internal_error"
                  }
                }
              }
            }
          },
          "503": {
            "description": "A dependency is unavailable, **or** a capability this deployment was never configured with — the message says which, and names the missing configuration by variable.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                },
                "example": {
                  "error": {
                    "type": "service_unavailable",
                    "message": "Storage is temporarily unavailable. Retry after the number of seconds in Retry-After, at most twice.",
                    "request_id": "req_01K1XQ8Z4Y7N3M2P6R9T5V0BWG",
                    "docs": "https://api.artifacthub.link/docs/errors#service_unavailable",
                    "retry_after": 5
                  }
                }
              }
            }
          }
        }
      }
    },
    "/v1/uploads/{upload_id}/complete": {
      "post": {
        "operationId": "complete_upload",
        "tags": [
          "upload"
        ],
        "summary": "Register the bytes you uploaded to a presigned URL and get the public URL back. Call this only after the PUT to the ticket's url succeeded; it is the second half of create_upload_ticket and is not usable on its own.",
        "description": "Verifies the stored object — size against the ticket's limit, checksum\nagainst the declared `sha256` — then publishes it and returns the same\nartifact JSON as every other create path.\n\n**Always 200, never 201, even for a brand-new artifact.** This call is\nidempotent by contract, so a retry after a timeout must answer identically\nto the call that timed out; a status that flipped on the second call would\nmake a correct retry look like a different outcome.\n\nAn object over the limit is rejected **and deleted** here. That is where the\nsize ceiling is really enforced, because a presigned `PUT` cannot enforce\none.\n\nThe body is ignored; send none.",
        "parameters": [
          {
            "name": "upload_id",
            "in": "path",
            "required": true,
            "description": "From the ticket. Prefer using `ticket.complete.url` whole rather than assembling this path.",
            "schema": {
              "type": "string"
            },
            "example": "up_01K1XQ9F3B2M7T5R0N6WYD8VJK"
          }
        ],
        "responses": {
          "200": {
            "description": "Published. Also returned unchanged on a repeat call for the same ticket.",
            "headers": {
              "Location": {
                "description": "The public URL.",
                "schema": {
                  "type": "string",
                  "format": "uri"
                }
              },
              "Cache-Control": {
                "description": "Always `no-store`. The URL is a secret in the shape of a link; nothing in between may keep a copy.",
                "schema": {
                  "type": "string"
                }
              },
              "X-Artifact-Id": {
                "schema": {
                  "type": "string"
                }
              },
              "X-Artifact-Url": {
                "schema": {
                  "type": "string",
                  "format": "uri"
                }
              },
              "X-Artifact-Version": {
                "schema": {
                  "type": "integer"
                }
              },
              "X-Artifact-Content-Sha256": {
                "description": "Absent if the post-write read-back failed.",
                "schema": {
                  "type": "string"
                }
              },
              "X-Artifact-Expires-At": {
                "description": "Absent — not the string `never` — when the artifact does not expire.",
                "schema": {
                  "type": "string",
                  "format": "date-time"
                }
              },
              "X-Artifact-Deduplicated": {
                "description": "`true` when identical bytes already existed in this context, so no new version was created.",
                "schema": {
                  "type": "boolean"
                }
              },
              "X-Request-Id": {
                "description": "On every response, success and failure alike.",
                "schema": {
                  "type": "string"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Artifact"
                },
                "example": {
                  "id": "quiet-harbor-3k9m2xq7wp4v8ntb5rjz6yfd0s",
                  "url": "https://quiet-harbor-3k9m2xq7wp4v8ntb5rjz6yfd0s.artifacthub.link/",
                  "version": 1,
                  "filename": "demo.mp4",
                  "content_type": "video/mp4",
                  "size": 2147483648,
                  "sha256": "9c1185a5c5e9fc54612808977ee8f548b2258d31a3b1f2c0e6d4a7b8c9d0e1f2",
                  "deduplicated": false,
                  "expires_at": "2026-09-03T10:12:33Z",
                  "created_at": "2026-08-04T10:12:33Z"
                }
              }
            }
          },
          "401": {
            "description": "No credential, or one that is malformed, unknown, revoked or expired. The `type` distinguishes them; only `missing_credentials` is fixable by the agent.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                },
                "example": {
                  "error": {
                    "type": "missing_credentials",
                    "message": "No token was sent. Set the ARTIFACT_HUB_TOKEN environment variable and send it as Authorization: Bearer $ARTIFACT_HUB_TOKEN. Never place a token on a command line.",
                    "request_id": "req_01K1XQ8Z4Y7N3M2P6R9T5V0BWC",
                    "docs": "https://api.artifacthub.link/docs/errors#missing_credentials"
                  }
                }
              }
            }
          },
          "403": {
            "description": "The key is valid but not permitted. Only the account owner can change this; do not retry and do not try another endpoint.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                },
                "example": {
                  "error": {
                    "type": "insufficient_scope",
                    "message": "This key does not have the artifacts:write scope. The account owner can add it to key 7Qm2xR4a. Do not retry this call.",
                    "request_id": "req_01K1XQ8Z4Y7N3M2P6R9T5V0BWD",
                    "docs": "https://api.artifacthub.link/docs/errors#insufficient_scope"
                  }
                }
              }
            }
          },
          "404": {
            "description": "No such ticket, or one belonging to another account. The two are indistinguishable on purpose, so the API cannot be used to enumerate ids.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                },
                "example": {
                  "error": {
                    "type": "not_found",
                    "message": "No upload ticket with that id. Tickets expire 15 minutes after they are minted; request a new one with POST /v1/uploads.",
                    "request_id": "req_01K1XQ8Z4Y7N3M2P6R9T5V0BWQ",
                    "docs": "https://api.artifacthub.link/docs/errors#not_found"
                  }
                }
              }
            }
          },
          "409": {
            "description": "The stored object's checksum does not match the `sha256` declared when the ticket was minted. Upload the file again.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                },
                "example": {
                  "error": {
                    "type": "conflict",
                    "message": "The uploaded object's checksum does not match the sha256 declared when the ticket was minted. Upload the file again with a fresh ticket.",
                    "request_id": "req_01K1XQ8Z4Y7N3M2P6R9T5V0BWR",
                    "docs": "https://api.artifacthub.link/docs/errors#conflict"
                  }
                }
              }
            }
          },
          "413": {
            "description": "The stored object is larger than 5 GB. It has been deleted.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                },
                "example": {
                  "error": {
                    "type": "payload_too_large",
                    "message": "The uploaded object is larger than the 5000000000 byte limit and has been deleted. Split the file and upload the parts separately.",
                    "request_id": "req_01K1XQ8Z4Y7N3M2P6R9T5V0BWS",
                    "docs": "https://api.artifacthub.link/docs/errors#payload_too_large"
                  }
                }
              }
            }
          },
          "429": {
            "description": "Rate limit (`rate_limited`, with `Retry-After`) or quota (`quota_exceeded`, deliberately **without** `Retry-After`, because waiting does not help).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                },
                "example": {
                  "error": {
                    "type": "rate_limited",
                    "message": "Too many requests. Wait 30 seconds and retry once. Do not retry in a loop.",
                    "request_id": "req_01K1XQ8Z4Y7N3M2P6R9T5V0BWE",
                    "docs": "https://api.artifacthub.link/docs/errors#rate_limited",
                    "retry_after": 30
                  }
                }
              }
            }
          },
          "500": {
            "description": "Ours. Retry once, then relay the `request_id` and stop.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                },
                "example": {
                  "error": {
                    "type": "internal_error",
                    "message": "An unexpected error occurred. Retry once; if it fails again, quote the request_id.",
                    "request_id": "req_01K1XQ8Z4Y7N3M2P6R9T5V0BWF",
                    "docs": "https://api.artifacthub.link/docs/errors#internal_error"
                  }
                }
              }
            }
          },
          "503": {
            "description": "A dependency is unavailable, **or** a capability this deployment was never configured with — the message says which, and names the missing configuration by variable.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                },
                "example": {
                  "error": {
                    "type": "service_unavailable",
                    "message": "Storage is temporarily unavailable. Retry after the number of seconds in Retry-After, at most twice.",
                    "request_id": "req_01K1XQ8Z4Y7N3M2P6R9T5V0BWG",
                    "docs": "https://api.artifacthub.link/docs/errors#service_unavailable",
                    "retry_after": 5
                  }
                }
              }
            }
          }
        }
      }
    },
    "/v1/artifacts/{id}": {
      "get": {
        "operationId": "get_artifact",
        "tags": [
          "manage"
        ],
        "summary": "Read one artifact's current state — title, visibility, expiry, current version. Use it to confirm an edit landed, or to check whether a link you published is still live.",
        "description": "The `id` is the public id: the first label of the artifact's hostname, and\nthe `id` field of every create response. Not the whole URL.\n\nAn artifact that has been deleted still answers here, with `deleted_at` set —\nthe delete is a tombstone, and the row is the caller's until the 30-day\nsweep purges it. The public URL stops serving immediately regardless.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "The artifact's public id — the `id` field of a create response, and the first label of its hostname. Not the whole URL, and not the filename.",
            "schema": {
              "type": "string"
            },
            "example": "quiet-harbor-3k9m2xq7wp4v8ntb5rjz6yfd0s"
          }
        ],
        "responses": {
          "200": {
            "description": "The artifact.",
            "headers": {
              "Cache-Control": {
                "description": "Always `no-store`. These bodies name public URLs, which are secrets in the shape of a link.",
                "schema": {
                  "type": "string"
                }
              },
              "X-Request-Id": {
                "description": "On every response, success and failure alike.",
                "schema": {
                  "type": "string"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ManagedArtifact"
                },
                "example": {
                  "id": "quiet-harbor-3k9m2xq7wp4v8ntb5rjz6yfd0s",
                  "url": "https://quiet-harbor-3k9m2xq7wp4v8ntb5rjz6yfd0s.artifacthub.link/",
                  "title": "Q3 summary",
                  "version": 2,
                  "filename": "report.html",
                  "content_type": "text/html; charset=utf-8",
                  "size": 48219,
                  "visibility": "public",
                  "comments_enabled": false,
                  "created_at": "2026-08-04T10:12:33Z",
                  "updated_at": "2026-08-04T10:12:33Z",
                  "expires_at": "2026-09-03T10:12:33Z",
                  "deleted_at": null
                }
              }
            }
          },
          "401": {
            "description": "No credential, or one that is malformed, unknown, revoked or expired. The `type` distinguishes them; only `missing_credentials` is fixable by the agent.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                },
                "example": {
                  "error": {
                    "type": "missing_credentials",
                    "message": "No token was sent. Set the ARTIFACT_HUB_TOKEN environment variable and send it as Authorization: Bearer $ARTIFACT_HUB_TOKEN. Never place a token on a command line.",
                    "request_id": "req_01K1XQ8Z4Y7N3M2P6R9T5V0BWC",
                    "docs": "https://api.artifacthub.link/docs/errors#missing_credentials"
                  }
                }
              }
            }
          },
          "403": {
            "description": "The key is valid but not permitted. Only the account owner can change this; do not retry and do not try another endpoint.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                },
                "example": {
                  "error": {
                    "type": "insufficient_scope",
                    "message": "This key does not have the artifacts:write scope. The account owner can add it to key 7Qm2xR4a. Do not retry this call.",
                    "request_id": "req_01K1XQ8Z4Y7N3M2P6R9T5V0BWD",
                    "docs": "https://api.artifacthub.link/docs/errors#insufficient_scope"
                  }
                }
              }
            }
          },
          "404": {
            "description": "No artifact with that id, **or** one this key cannot reach. The two are indistinguishable on purpose — a 403 would confirm the id is real.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                },
                "example": {
                  "error": {
                    "type": "not_found",
                    "message": "No artifact with id \"quiet-harbor-3k9m2xq7wp4v8ntb5rjz6yfd0s\". Run `ah list --json` to see the artifacts this key can reach. Do not guess ids.",
                    "request_id": "req_01K1XQ8Z4Y7N3M2P6R9T5V0BX3",
                    "docs": "https://api.artifacthub.link/docs/errors#not_found"
                  }
                }
              }
            }
          },
          "429": {
            "description": "Rate limit (`rate_limited`, with `Retry-After`) or quota (`quota_exceeded`, deliberately **without** `Retry-After`, because waiting does not help).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                },
                "example": {
                  "error": {
                    "type": "rate_limited",
                    "message": "Too many requests. Wait 30 seconds and retry once. Do not retry in a loop.",
                    "request_id": "req_01K1XQ8Z4Y7N3M2P6R9T5V0BWE",
                    "docs": "https://api.artifacthub.link/docs/errors#rate_limited",
                    "retry_after": 30
                  }
                }
              }
            }
          },
          "500": {
            "description": "Ours. Retry once, then relay the `request_id` and stop.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                },
                "example": {
                  "error": {
                    "type": "internal_error",
                    "message": "An unexpected error occurred. Retry once; if it fails again, quote the request_id.",
                    "request_id": "req_01K1XQ8Z4Y7N3M2P6R9T5V0BWF",
                    "docs": "https://api.artifacthub.link/docs/errors#internal_error"
                  }
                }
              }
            }
          },
          "503": {
            "description": "A dependency is unavailable, **or** a capability this deployment was never configured with — the message says which, and names the missing configuration by variable.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                },
                "example": {
                  "error": {
                    "type": "service_unavailable",
                    "message": "Storage is temporarily unavailable. Retry after the number of seconds in Retry-After, at most twice.",
                    "request_id": "req_01K1XQ8Z4Y7N3M2P6R9T5V0BWG",
                    "docs": "https://api.artifacthub.link/docs/errors#service_unavailable",
                    "retry_after": 5
                  }
                }
              }
            }
          }
        }
      },
      "patch": {
        "operationId": "update_artifact",
        "tags": [
          "manage"
        ],
        "summary": "Change an artifact's visibility, title or expiry. This is how you take a published link offline without destroying it — set visibility to private, and the URL stops serving on the very next request. It cannot change the filename or the bytes; publishing again with upload_file does that.",
        "description": "Every field is optional and at least one is required. A field you omit is\nleft alone.\n\n**`visibility: \"private\"` takes effect immediately**, on the next request to\nthe public URL, which then answers 404. It is reversible: set it back to\n`public` and the link works again. Nothing is deleted.\n\n**`expires_at` is a whole number of seconds since the unix epoch, or `null`**\nfor never. `null` and *omitted* are different: `null` clears an expiry,\nomitting the field leaves it as it is. The ISO timestamp this API renders in\nresponses is a display form and is rejected here — convert it with\n`Math.floor(Date.parse(s) / 1000)`.\n\n**A time in the past is accepted, deliberately.** The link answers 410 on the\nnext request and you can undo it by moving the date forward. That makes it\nthe reversible way to kill a link now; `delete_artifact` is not — it becomes\npermanent after 30 days.\n\n**Unknown fields are a 400, never ignored.** A misspelled `visibilty` that\nwas silently dropped would answer 200 with the artifact still public.\n\nThere is no `filename` field. `filename` belongs to a version, and versions\nare immutable, so publishing under a new name is a new version: call\n`upload_file` with `update=<id>`.\n\nReturns the whole artifact, not a diff — replace what you are holding.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "The artifact's public id — the `id` field of a create response, and the first label of its hostname. Not the whole URL, and not the filename.",
            "schema": {
              "type": "string"
            },
            "example": "quiet-harbor-3k9m2xq7wp4v8ntb5rjz6yfd0s"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/UpdateArtifactRequest"
              },
              "example": {
                "visibility": "private"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The artifact after the edit. Also returned unchanged when every field already held the value you sent.",
            "headers": {
              "Cache-Control": {
                "description": "Always `no-store`. These bodies name public URLs, which are secrets in the shape of a link.",
                "schema": {
                  "type": "string"
                }
              },
              "X-Request-Id": {
                "description": "On every response, success and failure alike.",
                "schema": {
                  "type": "string"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ManagedArtifact"
                },
                "example": {
                  "id": "quiet-harbor-3k9m2xq7wp4v8ntb5rjz6yfd0s",
                  "url": "https://quiet-harbor-3k9m2xq7wp4v8ntb5rjz6yfd0s.artifacthub.link/",
                  "title": "Q3 summary",
                  "version": 2,
                  "filename": "report.html",
                  "content_type": "text/html; charset=utf-8",
                  "size": 48219,
                  "visibility": "private",
                  "comments_enabled": false,
                  "created_at": "2026-08-04T10:12:33Z",
                  "updated_at": "2026-08-04T10:12:33Z",
                  "expires_at": "2026-09-03T10:12:33Z",
                  "deleted_at": null
                }
              }
            }
          },
          "400": {
            "description": "An empty body, an unknown field, a `filename`, a non-integer `expires_at`, or a `visibility` outside the two allowed values.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                },
                "example": {
                  "error": {
                    "type": "invalid_request",
                    "message": "filename cannot be patched: it lives on the immutable artifact_version row, so changing it is a new publish. Re-upload with PUT /v1/<new-name> and update=<id> to add a version under the new filename.",
                    "request_id": "req_01K1XQ8Z4Y7N3M2P6R9T5V0BWX",
                    "docs": "https://api.artifacthub.link/docs/errors#invalid_request"
                  }
                }
              }
            }
          },
          "401": {
            "description": "No credential, or one that is malformed, unknown, revoked or expired. The `type` distinguishes them; only `missing_credentials` is fixable by the agent.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                },
                "example": {
                  "error": {
                    "type": "missing_credentials",
                    "message": "No token was sent. Set the ARTIFACT_HUB_TOKEN environment variable and send it as Authorization: Bearer $ARTIFACT_HUB_TOKEN. Never place a token on a command line.",
                    "request_id": "req_01K1XQ8Z4Y7N3M2P6R9T5V0BWC",
                    "docs": "https://api.artifacthub.link/docs/errors#missing_credentials"
                  }
                }
              }
            }
          },
          "403": {
            "description": "The key is valid but not permitted. Only the account owner can change this; do not retry and do not try another endpoint.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                },
                "example": {
                  "error": {
                    "type": "insufficient_scope",
                    "message": "This key does not have the artifacts:write scope. The account owner can add it to key 7Qm2xR4a. Do not retry this call.",
                    "request_id": "req_01K1XQ8Z4Y7N3M2P6R9T5V0BWD",
                    "docs": "https://api.artifacthub.link/docs/errors#insufficient_scope"
                  }
                }
              }
            }
          },
          "404": {
            "description": "No artifact with that id, **or** one this key cannot reach. The two are indistinguishable on purpose — a 403 would confirm the id is real.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                },
                "example": {
                  "error": {
                    "type": "not_found",
                    "message": "No artifact with id \"quiet-harbor-3k9m2xq7wp4v8ntb5rjz6yfd0s\". Run `ah list --json` to see the artifacts this key can reach. Do not guess ids.",
                    "request_id": "req_01K1XQ8Z4Y7N3M2P6R9T5V0BX3",
                    "docs": "https://api.artifacthub.link/docs/errors#not_found"
                  }
                }
              }
            }
          },
          "410": {
            "description": "The artifact was deleted. A tombstoned artifact serves nothing to anybody, so its settings have no meaning; restore is not available either.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                },
                "example": {
                  "error": {
                    "type": "gone",
                    "message": "Artifact \"quiet-harbor-3k9m2xq7wp4v8ntb5rjz6yfd0s\" was deleted and can no longer be modified.",
                    "request_id": "req_01K1XQ8Z4Y7N3M2P6R9T5V0BWY",
                    "docs": "https://api.artifacthub.link/docs/errors#gone"
                  }
                }
              }
            }
          },
          "429": {
            "description": "Rate limit (`rate_limited`, with `Retry-After`) or quota (`quota_exceeded`, deliberately **without** `Retry-After`, because waiting does not help).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                },
                "example": {
                  "error": {
                    "type": "rate_limited",
                    "message": "Too many requests. Wait 30 seconds and retry once. Do not retry in a loop.",
                    "request_id": "req_01K1XQ8Z4Y7N3M2P6R9T5V0BWE",
                    "docs": "https://api.artifacthub.link/docs/errors#rate_limited",
                    "retry_after": 30
                  }
                }
              }
            }
          },
          "500": {
            "description": "Ours. Retry once, then relay the `request_id` and stop.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                },
                "example": {
                  "error": {
                    "type": "internal_error",
                    "message": "An unexpected error occurred. Retry once; if it fails again, quote the request_id.",
                    "request_id": "req_01K1XQ8Z4Y7N3M2P6R9T5V0BWF",
                    "docs": "https://api.artifacthub.link/docs/errors#internal_error"
                  }
                }
              }
            }
          },
          "503": {
            "description": "A dependency is unavailable, **or** a capability this deployment was never configured with — the message says which, and names the missing configuration by variable.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                },
                "example": {
                  "error": {
                    "type": "service_unavailable",
                    "message": "Storage is temporarily unavailable. Retry after the number of seconds in Retry-After, at most twice.",
                    "request_id": "req_01K1XQ8Z4Y7N3M2P6R9T5V0BWG",
                    "docs": "https://api.artifacthub.link/docs/errors#service_unavailable",
                    "retry_after": 5
                  }
                }
              }
            }
          }
        }
      },
      "delete": {
        "operationId": "delete_artifact",
        "tags": [
          "manage"
        ],
        "summary": "Retire an artifact for good. Prefer update_artifact with visibility private when you only want the link to stop working — that is reversible and this is not, after 30 days.",
        "description": "A tombstone, not an erasure: the public URL stops serving immediately and\nanswers 410, and the row survives for 30 days before the trash sweep purges\nthe bytes. There is no undelete endpoint.\n\nIdempotent — a repeat is another 204, not a 404, so a retry after a dropped\nresponse is free.\n\nRequires the `artifacts:delete` scope, which is deliberately separate from\n`artifacts:update`: a key can be issued that publishes and edits but cannot\ndestroy history.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "The artifact's public id — the `id` field of a create response, and the first label of its hostname. Not the whole URL, and not the filename.",
            "schema": {
              "type": "string"
            },
            "example": "quiet-harbor-3k9m2xq7wp4v8ntb5rjz6yfd0s"
          }
        ],
        "responses": {
          "204": {
            "description": "Deleted, or already deleted. No body.",
            "headers": {
              "Cache-Control": {
                "description": "Always `no-store`. These bodies name public URLs, which are secrets in the shape of a link.",
                "schema": {
                  "type": "string"
                }
              },
              "X-Request-Id": {
                "description": "On every response, success and failure alike.",
                "schema": {
                  "type": "string"
                }
              }
            }
          },
          "401": {
            "description": "No credential, or one that is malformed, unknown, revoked or expired. The `type` distinguishes them; only `missing_credentials` is fixable by the agent.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                },
                "example": {
                  "error": {
                    "type": "missing_credentials",
                    "message": "No token was sent. Set the ARTIFACT_HUB_TOKEN environment variable and send it as Authorization: Bearer $ARTIFACT_HUB_TOKEN. Never place a token on a command line.",
                    "request_id": "req_01K1XQ8Z4Y7N3M2P6R9T5V0BWC",
                    "docs": "https://api.artifacthub.link/docs/errors#missing_credentials"
                  }
                }
              }
            }
          },
          "403": {
            "description": "The key is valid but not permitted. Only the account owner can change this; do not retry and do not try another endpoint.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                },
                "example": {
                  "error": {
                    "type": "insufficient_scope",
                    "message": "This key does not have the artifacts:write scope. The account owner can add it to key 7Qm2xR4a. Do not retry this call.",
                    "request_id": "req_01K1XQ8Z4Y7N3M2P6R9T5V0BWD",
                    "docs": "https://api.artifacthub.link/docs/errors#insufficient_scope"
                  }
                }
              }
            }
          },
          "404": {
            "description": "No artifact with that id, **or** one this key cannot reach. The two are indistinguishable on purpose — a 403 would confirm the id is real.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                },
                "example": {
                  "error": {
                    "type": "not_found",
                    "message": "No artifact with id \"quiet-harbor-3k9m2xq7wp4v8ntb5rjz6yfd0s\". Run `ah list --json` to see the artifacts this key can reach. Do not guess ids.",
                    "request_id": "req_01K1XQ8Z4Y7N3M2P6R9T5V0BX3",
                    "docs": "https://api.artifacthub.link/docs/errors#not_found"
                  }
                }
              }
            }
          },
          "429": {
            "description": "Rate limit (`rate_limited`, with `Retry-After`) or quota (`quota_exceeded`, deliberately **without** `Retry-After`, because waiting does not help).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                },
                "example": {
                  "error": {
                    "type": "rate_limited",
                    "message": "Too many requests. Wait 30 seconds and retry once. Do not retry in a loop.",
                    "request_id": "req_01K1XQ8Z4Y7N3M2P6R9T5V0BWE",
                    "docs": "https://api.artifacthub.link/docs/errors#rate_limited",
                    "retry_after": 30
                  }
                }
              }
            }
          },
          "500": {
            "description": "Ours. Retry once, then relay the `request_id` and stop.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                },
                "example": {
                  "error": {
                    "type": "internal_error",
                    "message": "An unexpected error occurred. Retry once; if it fails again, quote the request_id.",
                    "request_id": "req_01K1XQ8Z4Y7N3M2P6R9T5V0BWF",
                    "docs": "https://api.artifacthub.link/docs/errors#internal_error"
                  }
                }
              }
            }
          },
          "503": {
            "description": "A dependency is unavailable, **or** a capability this deployment was never configured with — the message says which, and names the missing configuration by variable.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                },
                "example": {
                  "error": {
                    "type": "service_unavailable",
                    "message": "Storage is temporarily unavailable. Retry after the number of seconds in Retry-After, at most twice.",
                    "request_id": "req_01K1XQ8Z4Y7N3M2P6R9T5V0BWG",
                    "docs": "https://api.artifacthub.link/docs/errors#service_unavailable",
                    "retry_after": 5
                  }
                }
              }
            }
          }
        }
      }
    },
    "/v1/folders": {
      "post": {
        "operationId": "create_folder",
        "tags": [
          "manage"
        ],
        "summary": "Create a folder by path, creating any missing parents. Use it to file artifacts; the folder never appears in an artifact's public URL.",
        "description": "`mkdir -p`: every missing segment is created, every existing one is\nmatched. Matching folds case and accents, so `Relatórios` finds an\nexisting `relatorios` rather than making a second folder beside it — and\nthe folder that already exists keeps the spelling it was created with.\n\nIdempotent, so the response is 200 rather than 201 and does not say which\nsegments were new. Publishing with `path` creates the same folders, so\nthis endpoint is only needed for an EMPTY folder.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "path"
                ],
                "additionalProperties": false,
                "properties": {
                  "path": {
                    "type": "string",
                    "description": "A path from the root, like `reports/2026`. It cannot be empty or `/`: the root always exists."
                  }
                }
              },
              "example": {
                "path": "reports/2026"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The leaf folder, plus the whole chain from the root in order.",
            "headers": {
              "Cache-Control": {
                "description": "Always `no-store`. These bodies name public URLs, which are secrets in the shape of a link.",
                "schema": {
                  "type": "string"
                }
              },
              "X-Request-Id": {
                "description": "On every response, success and failure alike.",
                "schema": {
                  "type": "string"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/FolderCreated"
                },
                "example": {
                  "id": "0198f2c0-1b2c-7a3d-9e4f-5a6b7c8d9e0f",
                  "name": "2026",
                  "depth": 1,
                  "parent_folder_id": "0198f2c0-1b2c-7a3d-9e4f-5a6b7c8d9e00",
                  "path": [
                    {
                      "id": "0198f2c0-1b2c-7a3d-9e4f-5a6b7c8d9e00",
                      "name": "reports",
                      "depth": 0
                    },
                    {
                      "id": "0198f2c0-1b2c-7a3d-9e4f-5a6b7c8d9e0f",
                      "name": "2026",
                      "depth": 1
                    }
                  ]
                }
              }
            }
          },
          "400": {
            "description": "An empty path, a path deeper than the 100-level ceiling, or an unknown field.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                },
                "example": {
                  "error": {
                    "type": "invalid_request",
                    "message": "path names the folder to create and cannot be empty — the root always exists.",
                    "request_id": "req_01K1XQ8Z4Y7N3M2P6R9T5V0BF1",
                    "docs": "https://api.artifacthub.link/docs/errors#invalid_request"
                  }
                }
              }
            }
          },
          "401": {
            "description": "No credential, or one that is malformed, unknown, revoked or expired. The `type` distinguishes them; only `missing_credentials` is fixable by the agent.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                },
                "example": {
                  "error": {
                    "type": "missing_credentials",
                    "message": "No token was sent. Set the ARTIFACT_HUB_TOKEN environment variable and send it as Authorization: Bearer $ARTIFACT_HUB_TOKEN. Never place a token on a command line.",
                    "request_id": "req_01K1XQ8Z4Y7N3M2P6R9T5V0BWC",
                    "docs": "https://api.artifacthub.link/docs/errors#missing_credentials"
                  }
                }
              }
            }
          },
          "403": {
            "description": "The key is valid but not permitted. Only the account owner can change this; do not retry and do not try another endpoint.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                },
                "example": {
                  "error": {
                    "type": "insufficient_scope",
                    "message": "This key does not have the artifacts:write scope. The account owner can add it to key 7Qm2xR4a. Do not retry this call.",
                    "request_id": "req_01K1XQ8Z4Y7N3M2P6R9T5V0BWD",
                    "docs": "https://api.artifacthub.link/docs/errors#insufficient_scope"
                  }
                }
              }
            }
          },
          "409": {
            "description": "A concurrent create won the race for the same folded name.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                },
                "example": {
                  "error": {
                    "type": "conflict",
                    "message": "A folder that folds onto \"relatorios\" already exists here.",
                    "request_id": "req_01K1XQ8Z4Y7N3M2P6R9T5V0BF2",
                    "docs": "https://api.artifacthub.link/docs/errors#conflict"
                  }
                }
              }
            }
          },
          "429": {
            "description": "Rate limit (`rate_limited`, with `Retry-After`) or quota (`quota_exceeded`, deliberately **without** `Retry-After`, because waiting does not help).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                },
                "example": {
                  "error": {
                    "type": "rate_limited",
                    "message": "Too many requests. Wait 30 seconds and retry once. Do not retry in a loop.",
                    "request_id": "req_01K1XQ8Z4Y7N3M2P6R9T5V0BWE",
                    "docs": "https://api.artifacthub.link/docs/errors#rate_limited",
                    "retry_after": 30
                  }
                }
              }
            }
          },
          "500": {
            "description": "Ours. Retry once, then relay the `request_id` and stop.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                },
                "example": {
                  "error": {
                    "type": "internal_error",
                    "message": "An unexpected error occurred. Retry once; if it fails again, quote the request_id.",
                    "request_id": "req_01K1XQ8Z4Y7N3M2P6R9T5V0BWF",
                    "docs": "https://api.artifacthub.link/docs/errors#internal_error"
                  }
                }
              }
            }
          },
          "503": {
            "description": "A dependency is unavailable, **or** a capability this deployment was never configured with — the message says which, and names the missing configuration by variable.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                },
                "example": {
                  "error": {
                    "type": "service_unavailable",
                    "message": "Storage is temporarily unavailable. Retry after the number of seconds in Retry-After, at most twice.",
                    "request_id": "req_01K1XQ8Z4Y7N3M2P6R9T5V0BWG",
                    "docs": "https://api.artifacthub.link/docs/errors#service_unavailable",
                    "retry_after": 5
                  }
                }
              }
            }
          }
        }
      }
    },
    "/v1/folders/{folder_id}/children": {
      "get": {
        "operationId": "list_folder_children",
        "tags": [
          "manage"
        ],
        "summary": "List one level of the folder tree: its subfolders, and the artifacts filed directly in it.",
        "description": "`ls`, not `find` — one level only, never the subtree.\n\nPass `root` as the folder id for the top level; a path segment cannot be\nnull, so the root needs a name.\n\nSubfolders come back whole on every page; `cursor` and `limit` page the\nARTIFACTS. The rows are identical to `list_artifacts`, and searching or\nsorting is done there instead: `GET /v1/artifacts?folder_id=<id>&q=…`.\nPassing `q` or `sort` here is a 400 rather than an ignored filter.",
        "parameters": [
          {
            "name": "folder_id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "A folder id, or the literal `root`."
          },
          {
            "name": "cursor",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 100
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The subfolders and a page of artifacts.",
            "headers": {
              "Cache-Control": {
                "description": "Always `no-store`. These bodies name public URLs, which are secrets in the shape of a link.",
                "schema": {
                  "type": "string"
                }
              },
              "X-Request-Id": {
                "description": "On every response, success and failure alike.",
                "schema": {
                  "type": "string"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "folders",
                    "data",
                    "next_cursor"
                  ],
                  "properties": {
                    "folders": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/Folder"
                      }
                    },
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/Artifact"
                      }
                    },
                    "next_cursor": {
                      "type": [
                        "string",
                        "null"
                      ]
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "No credential, or one that is malformed, unknown, revoked or expired. The `type` distinguishes them; only `missing_credentials` is fixable by the agent.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                },
                "example": {
                  "error": {
                    "type": "missing_credentials",
                    "message": "No token was sent. Set the ARTIFACT_HUB_TOKEN environment variable and send it as Authorization: Bearer $ARTIFACT_HUB_TOKEN. Never place a token on a command line.",
                    "request_id": "req_01K1XQ8Z4Y7N3M2P6R9T5V0BWC",
                    "docs": "https://api.artifacthub.link/docs/errors#missing_credentials"
                  }
                }
              }
            }
          },
          "403": {
            "description": "The key is valid but not permitted. Only the account owner can change this; do not retry and do not try another endpoint.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                },
                "example": {
                  "error": {
                    "type": "insufficient_scope",
                    "message": "This key does not have the artifacts:write scope. The account owner can add it to key 7Qm2xR4a. Do not retry this call.",
                    "request_id": "req_01K1XQ8Z4Y7N3M2P6R9T5V0BWD",
                    "docs": "https://api.artifacthub.link/docs/errors#insufficient_scope"
                  }
                }
              }
            }
          },
          "404": {
            "description": "Unknown folder, another account's folder, or one in the trash — deliberately the same answer for all three.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                },
                "example": {
                  "error": {
                    "type": "not_found",
                    "message": "Folder \"0198f2c0-1b2c-7a3d-9e4f-5a6b7c8d9e0f\" not found.",
                    "request_id": "req_01K1XQ8Z4Y7N3M2P6R9T5V0BF3",
                    "docs": "https://api.artifacthub.link/docs/errors#not_found"
                  }
                }
              }
            }
          },
          "429": {
            "description": "Rate limit (`rate_limited`, with `Retry-After`) or quota (`quota_exceeded`, deliberately **without** `Retry-After`, because waiting does not help).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                },
                "example": {
                  "error": {
                    "type": "rate_limited",
                    "message": "Too many requests. Wait 30 seconds and retry once. Do not retry in a loop.",
                    "request_id": "req_01K1XQ8Z4Y7N3M2P6R9T5V0BWE",
                    "docs": "https://api.artifacthub.link/docs/errors#rate_limited",
                    "retry_after": 30
                  }
                }
              }
            }
          },
          "500": {
            "description": "Ours. Retry once, then relay the `request_id` and stop.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                },
                "example": {
                  "error": {
                    "type": "internal_error",
                    "message": "An unexpected error occurred. Retry once; if it fails again, quote the request_id.",
                    "request_id": "req_01K1XQ8Z4Y7N3M2P6R9T5V0BWF",
                    "docs": "https://api.artifacthub.link/docs/errors#internal_error"
                  }
                }
              }
            }
          },
          "503": {
            "description": "A dependency is unavailable, **or** a capability this deployment was never configured with — the message says which, and names the missing configuration by variable.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                },
                "example": {
                  "error": {
                    "type": "service_unavailable",
                    "message": "Storage is temporarily unavailable. Retry after the number of seconds in Retry-After, at most twice.",
                    "request_id": "req_01K1XQ8Z4Y7N3M2P6R9T5V0BWG",
                    "docs": "https://api.artifacthub.link/docs/errors#service_unavailable",
                    "retry_after": 5
                  }
                }
              }
            }
          }
        }
      }
    },
    "/v1/folders/{folder_id}/move": {
      "post": {
        "operationId": "move_folder",
        "tags": [
          "manage"
        ],
        "summary": "Move a folder under a different parent, carrying everything inside it. Use update_artifact with folder_id to move a single artifact.",
        "description": "The whole subtree moves with it. A folder cannot be moved into itself or\ninto anything beneath it — that would detach the branch from the root — and\nthe attempt is a 409 rather than a tree that later fails to list.\n\n`parent_folder_id` is required, and `null` is how you say the root. An\nomitted destination is a 400: a client that forgot to send one did not ask\nto empty the folder onto the top level.",
        "parameters": [
          {
            "name": "folder_id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "The folder to move. The root cannot be moved."
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "parent_folder_id"
                ],
                "additionalProperties": false,
                "properties": {
                  "parent_folder_id": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "description": "The destination folder, or `null` for the root. Required even when it is null."
                  }
                }
              },
              "example": {
                "parent_folder_id": "0198f2c0-1b2c-7a3d-9e4f-5a6b7c8d9e00"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Moved. `rewritten` counts the folder and every descendant.",
            "headers": {
              "Cache-Control": {
                "description": "Always `no-store`. These bodies name public URLs, which are secrets in the shape of a link.",
                "schema": {
                  "type": "string"
                }
              },
              "X-Request-Id": {
                "description": "On every response, success and failure alike.",
                "schema": {
                  "type": "string"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "id",
                    "parent_folder_id",
                    "depth",
                    "rewritten"
                  ],
                  "properties": {
                    "id": {
                      "type": "string"
                    },
                    "parent_folder_id": {
                      "type": [
                        "string",
                        "null"
                      ]
                    },
                    "depth": {
                      "type": "integer"
                    },
                    "rewritten": {
                      "type": "integer"
                    }
                  }
                },
                "example": {
                  "id": "0198f2c0-1b2c-7a3d-9e4f-5a6b7c8d9e0f",
                  "parent_folder_id": "0198f2c0-1b2c-7a3d-9e4f-5a6b7c8d9e00",
                  "depth": 1,
                  "rewritten": 4
                }
              }
            }
          },
          "400": {
            "description": "No destination, a move past the 100-level ceiling, or a subtree too large to rewrite in one batch.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                },
                "example": {
                  "error": {
                    "type": "invalid_request",
                    "message": "parent_folder_id is required. Send null to move the folder to the root.",
                    "request_id": "req_01K1XQ8Z4Y7N3M2P6R9T5V0BF4",
                    "docs": "https://api.artifacthub.link/docs/errors#invalid_request"
                  }
                }
              }
            }
          },
          "401": {
            "description": "No credential, or one that is malformed, unknown, revoked or expired. The `type` distinguishes them; only `missing_credentials` is fixable by the agent.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                },
                "example": {
                  "error": {
                    "type": "missing_credentials",
                    "message": "No token was sent. Set the ARTIFACT_HUB_TOKEN environment variable and send it as Authorization: Bearer $ARTIFACT_HUB_TOKEN. Never place a token on a command line.",
                    "request_id": "req_01K1XQ8Z4Y7N3M2P6R9T5V0BWC",
                    "docs": "https://api.artifacthub.link/docs/errors#missing_credentials"
                  }
                }
              }
            }
          },
          "403": {
            "description": "The key is valid but not permitted. Only the account owner can change this; do not retry and do not try another endpoint.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                },
                "example": {
                  "error": {
                    "type": "insufficient_scope",
                    "message": "This key does not have the artifacts:write scope. The account owner can add it to key 7Qm2xR4a. Do not retry this call.",
                    "request_id": "req_01K1XQ8Z4Y7N3M2P6R9T5V0BWD",
                    "docs": "https://api.artifacthub.link/docs/errors#insufficient_scope"
                  }
                }
              }
            }
          },
          "404": {
            "description": "The folder or the destination is unknown, another account's, or in the trash.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                },
                "example": {
                  "error": {
                    "type": "not_found",
                    "message": "Folder \"0198f2c0-1b2c-7a3d-9e4f-5a6b7c8d9e0f\" not found.",
                    "request_id": "req_01K1XQ8Z4Y7N3M2P6R9T5V0BF5",
                    "docs": "https://api.artifacthub.link/docs/errors#not_found"
                  }
                }
              }
            }
          },
          "409": {
            "description": "A cycle (moving a folder into its own descendant), or a name collision at the destination.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                },
                "example": {
                  "error": {
                    "type": "conflict",
                    "message": "Folder \"reports\" cannot be moved into \"2026\", which is inside it.",
                    "request_id": "req_01K1XQ8Z4Y7N3M2P6R9T5V0BF6",
                    "docs": "https://api.artifacthub.link/docs/errors#conflict"
                  }
                }
              }
            }
          },
          "429": {
            "description": "Rate limit (`rate_limited`, with `Retry-After`) or quota (`quota_exceeded`, deliberately **without** `Retry-After`, because waiting does not help).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                },
                "example": {
                  "error": {
                    "type": "rate_limited",
                    "message": "Too many requests. Wait 30 seconds and retry once. Do not retry in a loop.",
                    "request_id": "req_01K1XQ8Z4Y7N3M2P6R9T5V0BWE",
                    "docs": "https://api.artifacthub.link/docs/errors#rate_limited",
                    "retry_after": 30
                  }
                }
              }
            }
          },
          "500": {
            "description": "Ours. Retry once, then relay the `request_id` and stop.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                },
                "example": {
                  "error": {
                    "type": "internal_error",
                    "message": "An unexpected error occurred. Retry once; if it fails again, quote the request_id.",
                    "request_id": "req_01K1XQ8Z4Y7N3M2P6R9T5V0BWF",
                    "docs": "https://api.artifacthub.link/docs/errors#internal_error"
                  }
                }
              }
            }
          },
          "503": {
            "description": "A dependency is unavailable, **or** a capability this deployment was never configured with — the message says which, and names the missing configuration by variable.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                },
                "example": {
                  "error": {
                    "type": "service_unavailable",
                    "message": "Storage is temporarily unavailable. Retry after the number of seconds in Retry-After, at most twice.",
                    "request_id": "req_01K1XQ8Z4Y7N3M2P6R9T5V0BWG",
                    "docs": "https://api.artifacthub.link/docs/errors#service_unavailable",
                    "retry_after": 5
                  }
                }
              }
            }
          }
        }
      }
    },
    "/v1/folders/{folder_id}": {
      "patch": {
        "operationId": "rename_folder",
        "tags": [
          "manage"
        ],
        "summary": "Rename a folder. Everything inside it stays where it is — use move_folder to change where the folder itself lives.",
        "description": "The one edit `create_folder` cannot make: creating a folder that already\nexists matches on the folded name and keeps the spelling it was first\ngiven, so this is the only way to change a display name.\n\nRe-spelling a folder onto the same folded name — `relatorios` →\n`Relatórios` — is allowed and is the usual reason to call this. A\nDIFFERENT sibling that folds onto the same name is a 409.\n\nA rename changes the folder's PATH. An upload that passes the old name in\nits path will create a new folder rather than find this one, so treat a\nrename as a change of address. The response includes `normalized_name`,\nwhich is the segment a path is matched against.",
        "parameters": [
          {
            "name": "folder_id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "The folder to rename. The root has no name."
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "name"
                ],
                "additionalProperties": false,
                "properties": {
                  "name": {
                    "type": "string",
                    "description": "The new display name. One segment: `/` is not allowed in it."
                  }
                }
              },
              "example": {
                "name": "Relatórios"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Renamed. `name` is the spelling as stored, which may differ from what was sent (whitespace is collapsed and trimmed).",
            "headers": {
              "Cache-Control": {
                "description": "Always `no-store`. These bodies name public URLs, which are secrets in the shape of a link.",
                "schema": {
                  "type": "string"
                }
              },
              "X-Request-Id": {
                "description": "On every response, success and failure alike.",
                "schema": {
                  "type": "string"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "id",
                    "name",
                    "normalized_name"
                  ],
                  "properties": {
                    "id": {
                      "type": "string"
                    },
                    "name": {
                      "type": "string"
                    },
                    "normalized_name": {
                      "type": "string",
                      "description": "The folded name a path segment is matched against — lowercased, accents removed."
                    }
                  }
                },
                "example": {
                  "id": "0198f2c0-1b2c-7a3d-9e4f-5a6b7c8d9e0f",
                  "name": "Relatórios",
                  "normalized_name": "relatorios"
                }
              }
            }
          },
          "400": {
            "description": "An empty name, a name over 255 characters, or one containing `/`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                },
                "example": {
                  "error": {
                    "type": "invalid_request",
                    "message": "\"/\" separates path segments and cannot appear inside a folder name.",
                    "request_id": "req_01K1XQ8Z4Y7N3M2P6R9T5V0BF7",
                    "docs": "https://api.artifacthub.link/docs/errors#invalid_request"
                  }
                }
              }
            }
          },
          "401": {
            "description": "No credential, or one that is malformed, unknown, revoked or expired. The `type` distinguishes them; only `missing_credentials` is fixable by the agent.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                },
                "example": {
                  "error": {
                    "type": "missing_credentials",
                    "message": "No token was sent. Set the ARTIFACT_HUB_TOKEN environment variable and send it as Authorization: Bearer $ARTIFACT_HUB_TOKEN. Never place a token on a command line.",
                    "request_id": "req_01K1XQ8Z4Y7N3M2P6R9T5V0BWC",
                    "docs": "https://api.artifacthub.link/docs/errors#missing_credentials"
                  }
                }
              }
            }
          },
          "403": {
            "description": "The key is valid but not permitted. Only the account owner can change this; do not retry and do not try another endpoint.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                },
                "example": {
                  "error": {
                    "type": "insufficient_scope",
                    "message": "This key does not have the artifacts:write scope. The account owner can add it to key 7Qm2xR4a. Do not retry this call.",
                    "request_id": "req_01K1XQ8Z4Y7N3M2P6R9T5V0BWD",
                    "docs": "https://api.artifacthub.link/docs/errors#insufficient_scope"
                  }
                }
              }
            }
          },
          "404": {
            "description": "Unknown folder, another account's folder, or one in the trash.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                },
                "example": {
                  "error": {
                    "type": "not_found",
                    "message": "Folder \"0198f2c0-1b2c-7a3d-9e4f-5a6b7c8d9e0f\" not found.",
                    "request_id": "req_01K1XQ8Z4Y7N3M2P6R9T5V0BF8",
                    "docs": "https://api.artifacthub.link/docs/errors#not_found"
                  }
                }
              }
            }
          },
          "409": {
            "description": "Another folder in the same parent already has a name that folds to the same value.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                },
                "example": {
                  "error": {
                    "type": "conflict",
                    "message": "The root already contains \"Relatórios\", which is the same name as \"relatorios\" once case and accents are folded. Rename one of them first.",
                    "request_id": "req_01K1XQ8Z4Y7N3M2P6R9T5V0BF9",
                    "docs": "https://api.artifacthub.link/docs/errors#conflict"
                  }
                }
              }
            }
          },
          "429": {
            "description": "Rate limit (`rate_limited`, with `Retry-After`) or quota (`quota_exceeded`, deliberately **without** `Retry-After`, because waiting does not help).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                },
                "example": {
                  "error": {
                    "type": "rate_limited",
                    "message": "Too many requests. Wait 30 seconds and retry once. Do not retry in a loop.",
                    "request_id": "req_01K1XQ8Z4Y7N3M2P6R9T5V0BWE",
                    "docs": "https://api.artifacthub.link/docs/errors#rate_limited",
                    "retry_after": 30
                  }
                }
              }
            }
          },
          "500": {
            "description": "Ours. Retry once, then relay the `request_id` and stop.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                },
                "example": {
                  "error": {
                    "type": "internal_error",
                    "message": "An unexpected error occurred. Retry once; if it fails again, quote the request_id.",
                    "request_id": "req_01K1XQ8Z4Y7N3M2P6R9T5V0BWF",
                    "docs": "https://api.artifacthub.link/docs/errors#internal_error"
                  }
                }
              }
            }
          },
          "503": {
            "description": "A dependency is unavailable, **or** a capability this deployment was never configured with — the message says which, and names the missing configuration by variable.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                },
                "example": {
                  "error": {
                    "type": "service_unavailable",
                    "message": "Storage is temporarily unavailable. Retry after the number of seconds in Retry-After, at most twice.",
                    "request_id": "req_01K1XQ8Z4Y7N3M2P6R9T5V0BWG",
                    "docs": "https://api.artifacthub.link/docs/errors#service_unavailable",
                    "retry_after": 5
                  }
                }
              }
            }
          }
        }
      }
    },
    "/v1/public/artifacts/{id}": {
      "get": {
        "operationId": "get_public_artifact_metadata",
        "tags": [
          "manage"
        ],
        "summary": "Read the public metadata of an artifact by id — title, media type, byte size and current version — without a credential.",
        "description": "Anonymous and rate limited per client address. Returns 404 for a private\nor deleted artifact, which is the same answer an unknown id gets: the\nendpoint must not reveal which ids exist.\n\nNo content hash and no storage key, for the same reason the version list\nomits them — both would be usable across accounts to test whether somebody\nelse stored identical bytes.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "The artifact's public id — the `id` field of a create response, and the first label of its hostname. Not the whole URL, and not the filename.",
            "schema": {
              "type": "string"
            },
            "example": "quiet-harbor-3k9m2xq7wp4v8ntb5rjz6yfd0s"
          }
        ],
        "responses": {
          "200": {
            "description": "Public metadata.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "id": {
                      "type": "string"
                    },
                    "title": {
                      "type": "string"
                    },
                    "media_type": {
                      "type": "string"
                    },
                    "size_bytes": {
                      "type": "integer"
                    },
                    "version": {
                      "type": "integer"
                    }
                  }
                }
              }
            }
          },
          "404": {
            "description": "Unknown, private or deleted artifact — deliberately the same answer for all three.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                },
                "example": {
                  "error": {
                    "type": "not_found",
                    "message": "Artifact not found.",
                    "request_id": "req_public_metadata_404",
                    "docs": "https://api.artifacthub.link/docs/errors#not_found"
                  }
                }
              }
            }
          }
        }
      }
    },
    "/v1/artifacts/{id}/versions/{ordinal}/url": {
      "post": {
        "operationId": "mint_artifact_version_url",
        "tags": [
          "manage"
        ],
        "summary": "Mint a short-lived signed URL that serves one specific old version of an artifact.",
        "description": "The content host serves only the CURRENT version. This returns a signed\nURL on the content origin that serves the requested ordinal instead, for a\nlimited time.\n\nUse `list_artifact_versions` to find the ordinal. The URL carries its own\nauthorization in the signature, so it works without a credential — treat it\nas a secret and do not log it.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "The artifact's public id — the `id` field of a create response, and the first label of its hostname. Not the whole URL, and not the filename.",
            "schema": {
              "type": "string"
            },
            "example": "quiet-harbor-3k9m2xq7wp4v8ntb5rjz6yfd0s"
          },
          {
            "name": "ordinal",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer"
            },
            "description": "The version number from list_artifact_versions, not a version id."
          }
        ],
        "responses": {
          "200": {
            "description": "A signed, expiring URL for that version.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "url": {
                      "type": "string",
                      "format": "uri"
                    },
                    "expires_at": {
                      "type": "string",
                      "format": "date-time"
                    }
                  }
                }
              }
            }
          },
          "404": {
            "description": "Unknown artifact, or no version with that ordinal.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                },
                "example": {
                  "error": {
                    "type": "not_found",
                    "message": "Artifact version not found.",
                    "request_id": "req_version_url_404",
                    "docs": "https://api.artifacthub.link/docs/errors#not_found"
                  }
                }
              }
            }
          },
          "503": {
            "description": "This deployment has no version-URL signing key configured.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                },
                "example": {
                  "error": {
                    "type": "service_unavailable",
                    "message": "Signed version URLs are unavailable.",
                    "request_id": "req_version_url_503",
                    "docs": "https://api.artifacthub.link/docs/errors#service_unavailable"
                  }
                }
              }
            }
          }
        }
      }
    },
    "/v1/artifacts/{id}/versions": {
      "get": {
        "operationId": "list_artifact_versions",
        "tags": [
          "manage"
        ],
        "summary": "List an artifact's version history, newest first. Use it to find the version number to pass to restore_artifact_version after a bad publish.",
        "description": "One row per publish. `is_current` marks the version the public URL is\nserving; `version` is the ordinal `restore_artifact_version` takes.\n\nOld versions are **not** readable from the content host, which serves only\nthe current one — this is metadata, not a way to fetch an earlier copy.\n\nNo content hash and no storage key are returned. Both would be usable across\naccounts to test whether somebody else stored identical bytes.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "The artifact's public id — the `id` field of a create response, and the first label of its hostname. Not the whole URL, and not the filename.",
            "schema": {
              "type": "string"
            },
            "example": "quiet-harbor-3k9m2xq7wp4v8ntb5rjz6yfd0s"
          },
          {
            "name": "cursor",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 100
            }
          }
        ],
        "responses": {
          "200": {
            "description": "A page of versions, newest first.",
            "headers": {
              "Cache-Control": {
                "description": "Always `no-store`. These bodies name public URLs, which are secrets in the shape of a link.",
                "schema": {
                  "type": "string"
                }
              },
              "X-Request-Id": {
                "description": "On every response, success and failure alike.",
                "schema": {
                  "type": "string"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/VersionPage"
                },
                "example": {
                  "data": [
                    {
                      "version": 2,
                      "created_at": "2026-08-04T10:12:33Z",
                      "size": 48219,
                      "content_type": "text/html; charset=utf-8",
                      "filename": "report.html",
                      "restored_from": null,
                      "created_by": "api_key",
                      "is_current": true
                    }
                  ],
                  "next_cursor": null
                }
              }
            }
          },
          "401": {
            "description": "No credential, or one that is malformed, unknown, revoked or expired. The `type` distinguishes them; only `missing_credentials` is fixable by the agent.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                },
                "example": {
                  "error": {
                    "type": "missing_credentials",
                    "message": "No token was sent. Set the ARTIFACT_HUB_TOKEN environment variable and send it as Authorization: Bearer $ARTIFACT_HUB_TOKEN. Never place a token on a command line.",
                    "request_id": "req_01K1XQ8Z4Y7N3M2P6R9T5V0BWC",
                    "docs": "https://api.artifacthub.link/docs/errors#missing_credentials"
                  }
                }
              }
            }
          },
          "403": {
            "description": "The key is valid but not permitted. Only the account owner can change this; do not retry and do not try another endpoint.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                },
                "example": {
                  "error": {
                    "type": "insufficient_scope",
                    "message": "This key does not have the artifacts:write scope. The account owner can add it to key 7Qm2xR4a. Do not retry this call.",
                    "request_id": "req_01K1XQ8Z4Y7N3M2P6R9T5V0BWD",
                    "docs": "https://api.artifacthub.link/docs/errors#insufficient_scope"
                  }
                }
              }
            }
          },
          "404": {
            "description": "No artifact with that id, **or** one this key cannot reach. The two are indistinguishable on purpose — a 403 would confirm the id is real.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                },
                "example": {
                  "error": {
                    "type": "not_found",
                    "message": "No artifact with id \"quiet-harbor-3k9m2xq7wp4v8ntb5rjz6yfd0s\". Run `ah list --json` to see the artifacts this key can reach. Do not guess ids.",
                    "request_id": "req_01K1XQ8Z4Y7N3M2P6R9T5V0BX3",
                    "docs": "https://api.artifacthub.link/docs/errors#not_found"
                  }
                }
              }
            }
          },
          "429": {
            "description": "Rate limit (`rate_limited`, with `Retry-After`) or quota (`quota_exceeded`, deliberately **without** `Retry-After`, because waiting does not help).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                },
                "example": {
                  "error": {
                    "type": "rate_limited",
                    "message": "Too many requests. Wait 30 seconds and retry once. Do not retry in a loop.",
                    "request_id": "req_01K1XQ8Z4Y7N3M2P6R9T5V0BWE",
                    "docs": "https://api.artifacthub.link/docs/errors#rate_limited",
                    "retry_after": 30
                  }
                }
              }
            }
          },
          "500": {
            "description": "Ours. Retry once, then relay the `request_id` and stop.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                },
                "example": {
                  "error": {
                    "type": "internal_error",
                    "message": "An unexpected error occurred. Retry once; if it fails again, quote the request_id.",
                    "request_id": "req_01K1XQ8Z4Y7N3M2P6R9T5V0BWF",
                    "docs": "https://api.artifacthub.link/docs/errors#internal_error"
                  }
                }
              }
            }
          },
          "503": {
            "description": "A dependency is unavailable, **or** a capability this deployment was never configured with — the message says which, and names the missing configuration by variable.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                },
                "example": {
                  "error": {
                    "type": "service_unavailable",
                    "message": "Storage is temporarily unavailable. Retry after the number of seconds in Retry-After, at most twice.",
                    "request_id": "req_01K1XQ8Z4Y7N3M2P6R9T5V0BWG",
                    "docs": "https://api.artifacthub.link/docs/errors#service_unavailable",
                    "retry_after": 5
                  }
                }
              }
            }
          }
        }
      }
    },
    "/v1/artifacts/{id}/restore": {
      "post": {
        "operationId": "restore_artifact_version",
        "tags": [
          "manage"
        ],
        "summary": "Roll an artifact back to an earlier version by publishing its bytes again as a new version. Use after a bad upload; the URL never changes and nothing is overwritten.",
        "description": "Creates version N+1 pointing at version K's stored bytes. The history is\nappend-only, so a restore is itself a version and can be undone by restoring\nagain.\n\n`version` is the ordinal from `list_artifact_versions`, not an artifact id.\n\nRestoring the version that is already current is a no-op: `created` comes\nback `false` and no version is added.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "The artifact's public id — the `id` field of a create response, and the first label of its hostname. Not the whole URL, and not the filename.",
            "schema": {
              "type": "string"
            },
            "example": "quiet-harbor-3k9m2xq7wp4v8ntb5rjz6yfd0s"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "version"
                ],
                "additionalProperties": false,
                "properties": {
                  "version": {
                    "type": "integer",
                    "minimum": 1,
                    "description": "The ordinal shown as `version` by list_artifact_versions."
                  }
                }
              },
              "example": {
                "version": 1
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Restored, or already current.",
            "headers": {
              "Cache-Control": {
                "description": "Always `no-store`. These bodies name public URLs, which are secrets in the shape of a link.",
                "schema": {
                  "type": "string"
                }
              },
              "X-Request-Id": {
                "description": "On every response, success and failure alike.",
                "schema": {
                  "type": "string"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "id",
                    "url",
                    "version",
                    "restored_from",
                    "created"
                  ],
                  "properties": {
                    "id": {
                      "type": "string"
                    },
                    "url": {
                      "type": "string",
                      "format": "uri"
                    },
                    "version": {
                      "type": "integer",
                      "description": "The new current ordinal."
                    },
                    "restored_from": {
                      "type": "integer"
                    },
                    "created": {
                      "type": "boolean",
                      "description": "False when the requested version was already current, so nothing was added."
                    }
                  }
                },
                "example": {
                  "id": "quiet-harbor-3k9m2xq7wp4v8ntb5rjz6yfd0s",
                  "url": "https://quiet-harbor-3k9m2xq7wp4v8ntb5rjz6yfd0s.artifacthub.link/",
                  "version": 3,
                  "restored_from": 1,
                  "created": true
                }
              }
            }
          },
          "400": {
            "description": "No `version`, a non-integer one, or an unknown field.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                },
                "example": {
                  "error": {
                    "type": "invalid_request",
                    "message": "version is required and must be a whole number — the ordinal shown as \"version\" in GET /v1/artifacts/{id}/versions, not an artifact id.",
                    "request_id": "req_01K1XQ8Z4Y7N3M2P6R9T5V0BWZ",
                    "docs": "https://api.artifacthub.link/docs/errors#invalid_request"
                  }
                }
              }
            }
          },
          "401": {
            "description": "No credential, or one that is malformed, unknown, revoked or expired. The `type` distinguishes them; only `missing_credentials` is fixable by the agent.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                },
                "example": {
                  "error": {
                    "type": "missing_credentials",
                    "message": "No token was sent. Set the ARTIFACT_HUB_TOKEN environment variable and send it as Authorization: Bearer $ARTIFACT_HUB_TOKEN. Never place a token on a command line.",
                    "request_id": "req_01K1XQ8Z4Y7N3M2P6R9T5V0BWC",
                    "docs": "https://api.artifacthub.link/docs/errors#missing_credentials"
                  }
                }
              }
            }
          },
          "403": {
            "description": "The key is valid but not permitted. Only the account owner can change this; do not retry and do not try another endpoint.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                },
                "example": {
                  "error": {
                    "type": "insufficient_scope",
                    "message": "This key does not have the artifacts:write scope. The account owner can add it to key 7Qm2xR4a. Do not retry this call.",
                    "request_id": "req_01K1XQ8Z4Y7N3M2P6R9T5V0BWD",
                    "docs": "https://api.artifacthub.link/docs/errors#insufficient_scope"
                  }
                }
              }
            }
          },
          "404": {
            "description": "No artifact with that id, **or** one this key cannot reach. The two are indistinguishable on purpose — a 403 would confirm the id is real.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                },
                "example": {
                  "error": {
                    "type": "not_found",
                    "message": "No artifact with id \"quiet-harbor-3k9m2xq7wp4v8ntb5rjz6yfd0s\". Run `ah list --json` to see the artifacts this key can reach. Do not guess ids.",
                    "request_id": "req_01K1XQ8Z4Y7N3M2P6R9T5V0BX3",
                    "docs": "https://api.artifacthub.link/docs/errors#not_found"
                  }
                }
              }
            }
          },
          "410": {
            "description": "The artifact was deleted.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                },
                "example": {
                  "error": {
                    "type": "gone",
                    "message": "Artifact \"quiet-harbor-3k9m2xq7wp4v8ntb5rjz6yfd0s\" was deleted and can no longer be modified.",
                    "request_id": "req_01K1XQ8Z4Y7N3M2P6R9T5V0BX2",
                    "docs": "https://api.artifacthub.link/docs/errors#gone"
                  }
                }
              }
            }
          },
          "429": {
            "description": "Rate limit (`rate_limited`, with `Retry-After`) or quota (`quota_exceeded`, deliberately **without** `Retry-After`, because waiting does not help).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                },
                "example": {
                  "error": {
                    "type": "rate_limited",
                    "message": "Too many requests. Wait 30 seconds and retry once. Do not retry in a loop.",
                    "request_id": "req_01K1XQ8Z4Y7N3M2P6R9T5V0BWE",
                    "docs": "https://api.artifacthub.link/docs/errors#rate_limited",
                    "retry_after": 30
                  }
                }
              }
            }
          },
          "500": {
            "description": "Ours. Retry once, then relay the `request_id` and stop.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                },
                "example": {
                  "error": {
                    "type": "internal_error",
                    "message": "An unexpected error occurred. Retry once; if it fails again, quote the request_id.",
                    "request_id": "req_01K1XQ8Z4Y7N3M2P6R9T5V0BWF",
                    "docs": "https://api.artifacthub.link/docs/errors#internal_error"
                  }
                }
              }
            }
          },
          "503": {
            "description": "A dependency is unavailable, **or** a capability this deployment was never configured with — the message says which, and names the missing configuration by variable.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                },
                "example": {
                  "error": {
                    "type": "service_unavailable",
                    "message": "Storage is temporarily unavailable. Retry after the number of seconds in Retry-After, at most twice.",
                    "request_id": "req_01K1XQ8Z4Y7N3M2P6R9T5V0BWG",
                    "docs": "https://api.artifacthub.link/docs/errors#service_unavailable",
                    "retry_after": 5
                  }
                }
              }
            }
          }
        }
      }
    }
  },
  "components": {
    "securitySchemes": {
      "bearerAuth": {
        "type": "http",
        "scheme": "bearer",
        "description": "`Authorization: Bearer ah_live_...` — 54 characters, issued by the account\nowner. Test deployments issue `ah_test_` keys and a production deployment\nrejects them, and vice versa.\n\nRead it from the environment. Never place a key on a command line, in a URL,\nor in anything you emit."
      }
    },
    "schemas": {
      "Artifact": {
        "type": "object",
        "required": [
          "id",
          "url",
          "version",
          "filename",
          "content_type",
          "size",
          "sha256",
          "deduplicated",
          "expires_at",
          "created_at"
        ],
        "properties": {
          "id": {
            "type": "string",
            "description": "The public id. Base32, never contains a dot, and is also the DNS label the file is served from.",
            "example": "quiet-harbor-3k9m2xq7wp4v8ntb5rjz6yfd0s"
          },
          "url": {
            "type": "string",
            "format": "uri",
            "description": "The public URL. Report it exactly as returned; never rebuild it from the id.",
            "example": "https://quiet-harbor-3k9m2xq7wp4v8ntb5rjz6yfd0s.artifacthub.link/"
          },
          "version": {
            "type": "integer",
            "minimum": 1,
            "description": "1 for a new artifact; incremented when a context match produced a new version of an existing one.",
            "example": 1
          },
          "filename": {
            "type": [
              "string",
              "null"
            ],
            "description": "Metadata only — it never appears in the public URL. It is what decides the served content type.",
            "example": "report.html"
          },
          "content_type": {
            "type": "string",
            "description": "The type the file will actually be **served** with, recomputed from the stored bytes and the filename against a fixed allowlist. Not the `Content-Type` you sent, which is recorded and never trusted.",
            "example": "text/html; charset=utf-8"
          },
          "size": {
            "type": "integer",
            "minimum": 0,
            "example": 48219
          },
          "sha256": {
            "type": [
              "string",
              "null"
            ],
            "description": "Hex SHA-256 of the stored bytes.",
            "example": "9c1185a5c5e9fc54612808977ee8f548b2258d31a3b1f2c0e6d4a7b8c9d0e1f2"
          },
          "deduplicated": {
            "type": "boolean",
            "description": "True when identical bytes already existed in this context, so no new version was created and the URL is unchanged. A retry is therefore safe.",
            "example": false
          },
          "context_id": {
            "type": [
              "string",
              "null"
            ],
            "description": "Echoed back on the direct and inline paths. Absent from the presigned `complete` response.",
            "example": null
          },
          "expires_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time",
            "description": "Null when `expires` was `never`. Seconds precision, UTC.",
            "example": "2026-09-03T10:12:33Z"
          },
          "created_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time",
            "example": "2026-08-04T10:12:33Z"
          }
        }
      },
      "ManagedArtifact": {
        "type": "object",
        "required": [
          "id",
          "url",
          "title",
          "visibility"
        ],
        "description": "An artifact as the management surface sees it. Note `expires_at`, `created_at` and `updated_at` are ISO strings HERE, while `update_artifact` TAKES unix seconds — the display form and the wire form are different on purpose, and the PATCH rejects the string rather than parsing it.",
        "properties": {
          "id": {
            "type": "string",
            "description": "The public id. The internal row id is deliberately never returned."
          },
          "url": {
            "type": "string",
            "format": "uri"
          },
          "title": {
            "type": "string",
            "maxLength": 200,
            "description": "Empty string when never set. Editable with update_artifact."
          },
          "version": {
            "type": [
              "integer",
              "null"
            ],
            "description": "The current ordinal. Null only for an artifact whose first publish did not finish."
          },
          "filename": {
            "type": [
              "string",
              "null"
            ],
            "description": "From the current version, and NOT editable — a new filename is a new version."
          },
          "content_type": {
            "type": [
              "string",
              "null"
            ]
          },
          "size": {
            "type": [
              "integer",
              "null"
            ]
          },
          "visibility": {
            "type": "string",
            "enum": [
              "public",
              "private"
            ],
            "description": "`private` means the public URL answers 404 for everybody. Enforced on every request, not cached."
          },
          "comments_enabled": {
            "type": "boolean"
          },
          "view_count": {
            "type": "integer",
            "description": "Present on list rows only."
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          },
          "updated_at": {
            "type": "string",
            "format": "date-time"
          },
          "expires_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time",
            "description": "Null means never. A time in the past means the link answers 410."
          },
          "deleted_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time",
            "description": "Non-null for a tombstoned artifact. The bytes are purged 30 days later."
          }
        }
      },
      "ArtifactPage": {
        "type": "object",
        "required": [
          "data",
          "next_cursor"
        ],
        "properties": {
          "data": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/ManagedArtifact"
            }
          },
          "next_cursor": {
            "type": [
              "string",
              "null"
            ],
            "description": "Pass back as `cursor` for the next page. `null`, not absent, on the last page — one meaning, one shape."
          }
        }
      },
      "VersionPage": {
        "type": "object",
        "required": [
          "data",
          "next_cursor"
        ],
        "properties": {
          "data": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/ArtifactVersion"
            }
          },
          "next_cursor": {
            "type": [
              "string",
              "null"
            ]
          }
        }
      },
      "ArtifactVersion": {
        "type": "object",
        "required": [
          "version",
          "created_at",
          "is_current"
        ],
        "description": "One publish. Immutable: there is no endpoint that edits a version, which is why `filename` cannot be patched on the artifact.",
        "properties": {
          "version": {
            "type": "integer",
            "description": "The ordinal. This is what restore_artifact_version takes."
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          },
          "size": {
            "type": "integer"
          },
          "content_type": {
            "type": "string"
          },
          "filename": {
            "type": "string"
          },
          "restored_from": {
            "type": [
              "integer",
              "null"
            ],
            "description": "The ordinal this version was restored from, or null for an ordinary publish."
          },
          "created_by": {
            "type": "string",
            "enum": [
              "user",
              "api_key",
              "system"
            ]
          },
          "is_current": {
            "type": "boolean",
            "description": "True on exactly one row: the one being served."
          }
        }
      },
      "UpdateArtifactRequest": {
        "type": "object",
        "minProperties": 1,
        "additionalProperties": false,
        "description": "Every field optional, at least one required. Unknown fields are rejected with a 400 rather than ignored — a silently dropped `visibilty` answers 200 with the artifact still public.",
        "properties": {
          "visibility": {
            "type": "string",
            "enum": [
              "public",
              "private"
            ],
            "description": "`private` makes the public URL answer 404 on the next request. Reversible."
          },
          "title": {
            "type": "string",
            "maxLength": 200,
            "description": "The human name. Send `\"\"` to clear it. This is not the filename and does not affect the served bytes."
          },
          "expires_at": {
            "type": [
              "integer",
              "null"
            ],
            "description": "Seconds since the unix epoch, or `null` for never. Omitting the field leaves the expiry alone — `null` is not the same as absent. A time in the past is accepted and makes the link answer 410 immediately; it is the reversible way to take a link down."
          },
          "folder_id": {
            "type": [
              "string",
              "null"
            ],
            "description": "Move the artifact into this folder. `null` is the root; omitting the field leaves it where it is — the two are not the same. The destination must exist, belong to you and not be in the trash, or the call answers 404 with the message an unknown artifact gets. Create folders with create_folder, or with `path` on an upload."
          }
        }
      },
      "Folder": {
        "type": "object",
        "description": "One folder. The display name is the spelling it was FIRST created with — a later `mkdir -p` that folds onto it does not re-case it.",
        "properties": {
          "id": {
            "type": "string"
          },
          "name": {
            "type": "string"
          },
          "depth": {
            "type": "integer",
            "description": "0 at the root. The ceiling is 100 levels."
          }
        }
      },
      "FolderCreated": {
        "type": "object",
        "description": "The leaf folder, plus every folder on the path to it, root first.",
        "properties": {
          "id": {
            "type": "string"
          },
          "name": {
            "type": "string"
          },
          "depth": {
            "type": "integer"
          },
          "parent_folder_id": {
            "type": [
              "string",
              "null"
            ]
          },
          "path": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Folder"
            }
          }
        }
      },
      "CreateArtifactRequest": {
        "type": "object",
        "required": [
          "filename"
        ],
        "additionalProperties": false,
        "description": "Exactly one of `content` or `content_base64` is required. Sending both is a 400: they describe the same bytes two ways and there is no rule for which wins.",
        "properties": {
          "filename": {
            "type": "string",
            "description": "Required. Metadata only — never in the public URL — but it decides the served content type, so send the real name including its extension.",
            "example": "summary.html"
          },
          "content": {
            "type": "string",
            "description": "UTF-8 text. Capped at 25 MB."
          },
          "content_base64": {
            "type": "string",
            "description": "Standard base64 with padding (RFC 4648 §4), for binary payloads. Capped at 25 MB decoded."
          },
          "content_type": {
            "type": "string",
            "description": "Recorded for forensics. The served type is recomputed and this value is never trusted for it."
          },
          "context_id": {
            "type": "string",
            "maxLength": 128
          },
          "expires": {
            "type": "string",
            "enum": [
              "12h",
              "7d",
              "30d",
              "never"
            ],
            "default": "30d"
          },
          "new_link": {
            "type": "boolean"
          },
          "path": {
            "type": "string",
            "maxLength": 1024,
            "description": "Folder to file the artifact under, from the root, creating what does not exist (`mkdir -p`). Spelled `path` here and `path` in the PUT query string too — it is one word, so the two conventions agree. IGNORED on a republication: the folder is set when an artifact is born. Refile with `PATCH /v1/artifacts/{id}`.",
            "example": "reports/2026"
          },
          "title": {
            "type": "string",
            "maxLength": 200
          },
          "update": {
            "type": "string"
          },
          "vanity": {
            "type": "string",
            "deprecated": true,
            "description": "NOT IMPLEMENTED. Always rejected with 400 rather than silently dropped. Omit it."
          }
        }
      },
      "CreateUploadTicketRequest": {
        "type": "object",
        "required": [
          "filename",
          "size"
        ],
        "additionalProperties": false,
        "properties": {
          "filename": {
            "type": "string",
            "maxLength": 255,
            "example": "demo.mp4"
          },
          "size": {
            "type": "integer",
            "minimum": 0,
            "maximum": 5000000000,
            "description": "Required: the file's exact length in bytes. It is the only thing that lets an impossible upload be refused before a URL is issued.",
            "example": 2147483648
          },
          "content_type": {
            "type": "string",
            "example": "video/mp4"
          },
          "sha256": {
            "type": "string",
            "pattern": "^[0-9a-f]{64}$",
            "description": "Lowercase hex SHA-256 of the file. Strongly recommended: it turns `complete_upload` into a metadata write instead of a server-side copy, and it is what the checksum verification at completion compares against."
          },
          "context_id": {
            "type": "string",
            "maxLength": 128
          },
          "expires": {
            "type": "string",
            "enum": [
              "12h",
              "7d",
              "30d",
              "never"
            ],
            "default": "30d"
          },
          "new_link": {
            "type": "boolean"
          },
          "path": {
            "type": "string",
            "maxLength": 1024,
            "description": "Folder to file the artifact under, from the root, creating what does not exist (`mkdir -p`). Spelled `path` here and `path` in the PUT query string too — it is one word, so the two conventions agree. IGNORED on a republication: the folder is set when an artifact is born. Refile with `PATCH /v1/artifacts/{id}`.",
            "example": "reports/2026"
          },
          "title": {
            "type": "string",
            "maxLength": 200
          },
          "update": {
            "type": "string"
          },
          "vanity": {
            "type": "string",
            "deprecated": true,
            "description": "NOT IMPLEMENTED. Always rejected with 400. Omit it."
          }
        }
      },
      "UploadTicket": {
        "type": "object",
        "required": [
          "upload_id",
          "method",
          "url",
          "headers",
          "expires_at",
          "max_bytes",
          "complete"
        ],
        "properties": {
          "upload_id": {
            "type": "string"
          },
          "method": {
            "type": "string",
            "description": "The method to use against `url`. Use it as given.",
            "example": "PUT"
          },
          "url": {
            "type": "string",
            "format": "uri",
            "description": "The presigned URL, on the storage service's own host — NOT this API's host. Send no Authorization header to it; the signature is the credential."
          },
          "headers": {
            "type": "object",
            "additionalProperties": {
              "type": "string"
            },
            "description": "Copy verbatim onto the PUT. These are covered by the signature, so an added or altered header is a bare 403."
          },
          "expires_at": {
            "type": "string",
            "format": "date-time",
            "description": "15 minutes after minting."
          },
          "max_bytes": {
            "type": "integer",
            "description": "Advisory only — a presigned PUT cannot enforce a size cap. The limit is enforced at complete_upload, which deletes what it rejects.",
            "example": 5000000000
          },
          "complete": {
            "type": "object",
            "required": [
              "method",
              "url"
            ],
            "description": "The second call, absolute and built from the origin you asked on. Use it whole.",
            "properties": {
              "method": {
                "type": "string",
                "example": "POST"
              },
              "url": {
                "type": "string",
                "format": "uri"
              }
            }
          }
        }
      },
      "ErrorEnvelope": {
        "type": "object",
        "required": [
          "error"
        ],
        "description": "One envelope for every failure on every route. `error.message` carries the remediation; relay it rather than the status code.",
        "properties": {
          "error": {
            "type": "object",
            "required": [
              "type",
              "message",
              "request_id"
            ],
            "properties": {
              "type": {
                "type": "string",
                "enum": [
                  "invalid_request",
                  "invalid_token",
                  "token_revoked",
                  "token_expired",
                  "missing_credentials",
                  "insufficient_scope",
                  "csrf_blocked",
                  "account_disabled",
                  "not_found",
                  "conflict",
                  "gone",
                  "payload_too_large",
                  "unsupported_media_type",
                  "range_not_satisfiable",
                  "unprocessable_entity",
                  "rate_limited",
                  "quota_exceeded",
                  "internal_error",
                  "service_unavailable"
                ],
                "description": "Stable machine identifier. Append-only — a value here is never renamed or removed once shipped, so it is safe to branch on."
              },
              "message": {
                "type": "string",
                "description": "One or two sentences addressed to whoever can fix it, naming the fix — often the exact endpoint or flag to switch to. This is the field to read and to relay."
              },
              "request_id": {
                "type": "string",
                "pattern": "^req_[0-9A-HJKMNP-TV-Z]{26}$",
                "description": "Also on every response as the `X-Request-Id` header, success and failure alike. The only string a user needs to quote in a bug report."
              },
              "docs": {
                "type": "string",
                "format": "uri",
                "description": "Deep link. The documentation site it points at does not exist yet — read `message` instead."
              },
              "details": {
                "type": "array",
                "description": "Per-field breakdown on validation failures.",
                "items": {
                  "type": "object",
                  "required": [
                    "field",
                    "issue"
                  ],
                  "properties": {
                    "field": {
                      "type": "string"
                    },
                    "issue": {
                      "type": "string"
                    }
                  }
                }
              },
              "retry_after": {
                "type": "integer",
                "description": "Seconds; mirrors the `Retry-After` header. Deliberately absent on `quota_exceeded`, because waiting does not help there."
              }
            }
          }
        }
      }
    }
  }
}
