{
  "service": "bytes",
  "contract": "2026-09-10",
  "doc": "The operation table of ulab-standard/API.md: one row per surface verb and one per operational route of ulab-standard/OPERATIONS.md, rendered from bnbytes.Operations. Verbs that share a route share its method and path. Parameters travel in the HTTP path where the template names them and in the query otherwise. The prose contract is SURFACE.md; this file cannot disagree with the router, the SDK or the schemas, because contract_test.go checks each pair.",
  "surface": "SURFACE.md",
  "bindings": {
    "http": "HTTP.md"
  },
  "operations": [
    {
      "name": "get",
      "kind": "read",
      "doc": "the bytes of one blob",
      "params": [
        {
          "name": "name",
          "kind": "string",
          "required": true,
          "doc": "sha256:\u003c64 lowercase hex\u003e"
        }
      ],
      "response": "bytes",
      "errors": [
        "not_found"
      ],
      "http": {
        "method": "GET",
        "path": "/v1/blobs/{name}"
      }
    },
    {
      "name": "get_range",
      "kind": "read",
      "doc": "the overlap of a byte range with one blob; past the end is a short read",
      "params": [
        {
          "name": "name",
          "kind": "string",
          "required": true,
          "doc": "sha256:\u003c64 lowercase hex\u003e"
        },
        {
          "name": "offset",
          "kind": "int",
          "required": false,
          "doc": "first byte, default 0"
        },
        {
          "name": "length",
          "kind": "int",
          "required": false,
          "doc": "byte count, default to the end"
        }
      ],
      "response": "bytes",
      "errors": [
        "not_found"
      ],
      "http": {
        "method": "GET",
        "path": "/v1/blobs/{name}"
      }
    },
    {
      "name": "get_stream",
      "kind": "stream",
      "doc": "the bytes of one blob, incrementally; cancellation is the client closing the connection",
      "params": [
        {
          "name": "name",
          "kind": "string",
          "required": true,
          "doc": "sha256:\u003c64 lowercase hex\u003e"
        }
      ],
      "response": "bytes",
      "errors": [
        "not_found"
      ],
      "http": {
        "method": "GET",
        "path": "/v1/blobs/{name}"
      }
    },
    {
      "name": "has",
      "kind": "read",
      "doc": "which of the names are here: presence of a key in sizes",
      "params": [],
      "request": "StatRequest",
      "response": "StatResponse",
      "errors": [],
      "http": {
        "method": "POST",
        "path": "/v1/stat"
      }
    },
    {
      "name": "size",
      "kind": "read",
      "doc": "byte count per name that is here",
      "params": [],
      "request": "StatRequest",
      "response": "StatResponse",
      "errors": [],
      "http": {
        "method": "POST",
        "path": "/v1/stat"
      }
    },
    {
      "name": "put",
      "kind": "mutation",
      "doc": "store bytes under their hash; idempotent",
      "params": [],
      "request": "bytes",
      "response": "PutResponse",
      "errors": [
        "too_large"
      ],
      "http": {
        "method": "POST",
        "path": "/v1/blobs"
      }
    },
    {
      "name": "put_stream",
      "kind": "mutation",
      "doc": "store bytes arriving incrementally; commits atomically at clean end of body, an abort commits nothing",
      "params": [],
      "request": "bytes",
      "response": "PutResponse",
      "errors": [
        "too_large"
      ],
      "http": {
        "method": "POST",
        "path": "/v1/blobs"
      }
    },
    {
      "name": "get_many",
      "kind": "read",
      "doc": "the blobs that are here among the names, as records keyed by name, unordered",
      "params": [],
      "request": "NamesRequest",
      "response": "records",
      "errors": [],
      "http": {
        "method": "POST",
        "path": "/v1/get"
      }
    },
    {
      "name": "put_many",
      "kind": "mutation",
      "doc": "store each anonymous record independently; results in request order",
      "params": [],
      "request": "records",
      "response": "PutManyResponse",
      "errors": [],
      "http": {
        "method": "POST",
        "path": "/v1/put"
      }
    },
    {
      "name": "livez",
      "kind": "operational",
      "doc": "the process is up and can answer",
      "params": [],
      "response": "Health",
      "errors": [],
      "http": {
        "method": "GET",
        "path": "/livez"
      }
    },
    {
      "name": "healthz",
      "kind": "operational",
      "doc": "the service can serve its contract: every dependency check passes",
      "params": [],
      "response": "Health",
      "errors": [],
      "http": {
        "method": "GET",
        "path": "/healthz"
      }
    },
    {
      "name": "describe",
      "kind": "operational",
      "doc": "who this is, which contract, which build, which bindings, which operations, which refusals",
      "params": [],
      "response": "Describe",
      "errors": [],
      "http": {
        "method": "GET",
        "path": "/describe"
      }
    },
    {
      "name": "describe_file",
      "kind": "operational",
      "doc": "one committed contract file, verbatim",
      "params": [
        {
          "name": "file",
          "kind": "string",
          "required": true,
          "doc": "a name listed by describe.files"
        }
      ],
      "response": "file",
      "errors": [
        "not_found"
      ],
      "http": {
        "method": "GET",
        "path": "/describe/{file...}"
      }
    }
  ],
  "refusals": [
    {
      "name": "not_found",
      "http": 404,
      "meaning": "the name asked for is not here"
    },
    {
      "name": "too_large",
      "http": 413,
      "meaning": "the bytes exceed what this deployment accepts"
    },
    {
      "name": "bad_request",
      "http": 400,
      "meaning": "a malformed name or request body: a protocol error of this binding, not a refusal of content"
    }
  ]
}
