{"id":"71ced38ac86fc754e1571058671f308303b6dd5b4bf15bcb38f97a28caac8c13","pubkey":"1336a17e161d0e8af2b68ee95ad2a479fc38bef96a17d6127ea02a40d28dd97e","created_at":1775842488,"kind":30817,"tags":[["d","bud-04"],["title","BUD-04: Mirroring blobs"],["summary","Defines PUT /mirror, which asks a server to fetch a blob from a URL itself instead of having the client upload the same bytes twice."],["s","draft"],["t","blossom"],["t","bud"],["alt","A specification: BUD-04: Mirroring blobs"],["client","openspecs-import"],["published_at","1717109416"],["proxy","https://github.com/hzrd149/blossom/blob/b5bd2801d1763aa635fc8fea7a76597e0eb18990/buds/04.md","web"],["x","859819ab5b0d2d00972ac649743974bae8ff56d7df1f1bd1d8b627de481a8622"]],"content":"# BUD-04\n\n## Mirroring blobs\n\n`draft` `optional`\n\nDefines the `/mirror` endpoint\n\n## PUT /mirror - Mirror Blob\n\nA server MAY expose a `PUT /mirror` endpoint to allow users to copy a blob from a URL instead of uploading it\n\nClients MUST pass the URL of the remote blob as a stringified JSON object in the request body\n\n```jsonc\n// request body...\n{\n  \"url\": \"https://cdn.satellite.earth/b1674191a88ec5cdd733e4240a81803105dc412d6c6708d53ab94fc248f4f553.pdf\"\n}\n```\n\nIf the blob was newly mirrored and stored, the endpoint MUST respond with `201 Created` and a [Blob Descriptor](#blob-descriptor) in the response body.\nIf the blob already exists, the endpoint MUST respond with `200 OK` and a [Blob Descriptor](#blob-descriptor) in the response body.\n\nThe destination server SHOULD use the `Content-Type` header returned from the origin server to infer the mime type of\nthe blob. If the `Content-Type` header is not present the destination server SHOULD attempt to detect the `Content-Type`\nfrom the blob contents and file extension, falling back to `application/octet-stream` if it cannot determine the type.\n\nServers MAY use the `Content-Length` header to determine the size of the blob.\n\nServers MAY reject a mirror request for any reason and MUST respond with the appropriate HTTP status code and an error message explaining the reason for the rejection.\n\n### Status codes\n\nServers SHOULD use the following status codes for `PUT /mirror` responses:\n\n| Status Code                  | Meaning                                                                                                      |\n| ---------------------------- | ------------------------------------------------------------------------------------------------------------ |\n| `200 OK`                     | The blob already exists and the server is returning the existing [Blob Descriptor](nostr:naddr1qvzqqqrcvypzqyek59lpv8gw3tetdrhfttf2g70u8zl0j6sh6cf8agp2grfgmkt7qqrxyaty95cryxk74nh#blob-descriptor). |\n| `201 Created`                | The blob was mirrored and stored successfully and the server is returning its [Blob Descriptor](nostr:naddr1qvzqqqrcvypzqyek59lpv8gw3tetdrhfttf2g70u8zl0j6sh6cf8agp2grfgmkt7qqrxyaty95cryxk74nh#blob-descriptor). |\n| `400 Bad Request`            | The request body is malformed or the `url` is invalid.                                                       |\n| `401 Unauthorized`           | Authorization is required and missing or invalid. See [BUD-11](nostr:naddr1qvzqqqrcvypzqyek59lpv8gw3tetdrhfttf2g70u8zl0j6sh6cf8agp2grfgmkt7qqrxyaty95cnzuvmwmm#endpoint-authorization-requirements). |\n| `402 Payment Required`       | Payment is required before mirroring can proceed. See [BUD-07](nostr:naddr1qvzqqqrcvypzqyek59lpv8gw3tetdrhfttf2g70u8zl0j6sh6cf8agp2grfgmkt7qqrxyaty95crwt8m678).                                    |\n| `403 Forbidden`              | The request is understood but not allowed by server policy.                                                  |\n| `409 Conflict`               | The mirrored blob hash does not match the authorized `x` tag.                                                |\n| `413 Content Too Large`      | The mirrored blob exceeds server size limits.                                                                |\n| `415 Unsupported Media Type` | The mirrored blob type is not supported.                                                                     |\n| `429 Too Many Requests`      | The client has exceeded a rate limit or quota.                                                               |\n| `502 Bad Gateway`            | The server could not fetch the blob from the origin URL or the origin response was unusable.                |\n\nIf included, `X-Reason` MUST be treated as a human readable diagnostic message only and clients MUST NOT parse it for control flow.\n\n### Upload Authorization\n\nServers MAY require authorization when mirroring blobs as defined by [BUD-11](nostr:naddr1qvzqqqrcvypzqyek59lpv8gw3tetdrhfttf2g70u8zl0j6sh6cf8agp2grfgmkt7qqrxyaty95cnzuvmwmm#endpoint-authorization-requirements).\n\n## Example Flow\n\n1. Client signs an `upload` authorization token and uploads blob to Server A\n1. Server A returns a [Blob Descriptor](nostr:naddr1qvzqqqrcvypzqyek59lpv8gw3tetdrhfttf2g70u8zl0j6sh6cf8agp2grfgmkt7qqrxyaty95cryxk74nh#blob-descriptor) with the `url`\n1. Client sends the `url` to Server B `/mirror` using the original `upload` authorization token\n1. Server B downloads the blob from Server A using the `url`\n1. Server B verifies the downloaded blob hash matches the `x` tag in the authorization token\n1. Server B returns a [Blob Descriptor](nostr:naddr1qvzqqqrcvypzqyek59lpv8gw3tetdrhfttf2g70u8zl0j6sh6cf8agp2grfgmkt7qqrxyaty95cryxk74nh#blob-descriptor)\n","sig":"181a0761ca1158a599f8c00e4a9bdd71bec14722453afe4bfffaa5566fab9d90a6a1cf8550b50cc4a58a88049be61882a7b1943e6eeaf92aec5dbd7311797225"}