{
  "openapi": "3.1.0",
  "info": {
    "title": "GradientArt design handoffs",
    "version": "1.0.0",
    "description": "Validate agent-generated artwork and open it as an unsaved project. Every handoff uses temporary storage and a bearer token, expires after one hour, and never creates a saved project. No authentication is required. See /agents/designs.md for authoring rules, quotas and retention."
  },
  "servers": [{ "url": "https://gra.dient.art" }],
  "security": [],
  "paths": {
    "/api/v1/designs/prepare": {
      "post": {
        "operationId": "prepareDesign",
        "summary": "Prepare a temporary editor link for a generated design",
        "description": "Accept a complete v1 JSON document, at most 4 MiB uncompressed UTF-8. Validate structure and semantic relationships before storing a one-hour handoff. Return an editor URL for the user to open; no permanent project is created. Limits: 20/hour per client IP and 100/hour globally.",
        "requestBody": {
          "required": true,
          "content": { "application/json": { "schema": { "$ref": "https://gra.dient.art/schemas/v1/project.schema.json" } } }
        },
        "responses": {
          "201": {
            "description": "Prepared; anyone with the URL can access the design until expiresAt.",
            "headers": { "Cache-Control": { "schema": { "const": "no-store" } } },
            "content": { "application/json": { "schema": { "$ref": "#/components/schemas/PreparedDesign" } } }
          },
          "400": { "$ref": "#/components/responses/InvalidRequest" },
          "405": { "$ref": "#/components/responses/MethodNotAllowed" },
          "413": { "$ref": "#/components/responses/PayloadTooLarge" },
          "415": { "$ref": "#/components/responses/UnsupportedMedia" },
          "422": { "$ref": "#/components/responses/InvalidDesign" },
          "429": { "$ref": "#/components/responses/RateLimited" },
          "503": { "$ref": "#/components/responses/Unavailable" }
        }
      }
    },
    "/api/v1/designs/resolve": {
      "post": {
        "operationId": "resolveDesign",
        "summary": "Resolve an unexpired design token without consuming it",
        "description": "Used by the editor. No cookies are needed. Request limit: 1024 bytes. Repeated reads do not extend the one-hour lifetime. Missing and expired tokens return the same 410 error. Limits: 60/minute per client IP and 600/minute globally.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object", "additionalProperties": false, "required": ["token"],
                "properties": { "token": { "type": "string", "pattern": "^[a-f0-9]{64}$" } }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The original versioned design document. This is not a saved project.",
            "headers": { "Cache-Control": { "schema": { "const": "no-store" } } },
            "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ResolvedDesign" } } }
          },
          "400": { "$ref": "#/components/responses/InvalidRequest" },
          "405": { "$ref": "#/components/responses/MethodNotAllowed" },
          "410": { "$ref": "#/components/responses/Expired" },
          "413": { "$ref": "#/components/responses/PayloadTooLarge" },
          "415": { "$ref": "#/components/responses/UnsupportedMedia" },
          "429": { "$ref": "#/components/responses/RateLimited" },
          "503": { "$ref": "#/components/responses/Unavailable" }
        }
      }
    }
  },
  "components": {
    "schemas": {
      "PreparedDesign": {
        "type": "object", "additionalProperties": false, "required": ["editorUrl", "expiresAt", "saved"],
        "properties": {
          "editorUrl": { "type": "string", "format": "uri", "description": "Open this exact URL. Its fragment contains the bearer token, not the design JSON." },
          "expiresAt": { "type": "string", "format": "date-time" },
          "saved": { "const": false }
        }
      },
      "ResolvedDesign": {
        "type": "object", "additionalProperties": false, "required": ["file", "expiresAt", "saved"],
        "properties": {
          "file": { "$ref": "https://gra.dient.art/schemas/v1/project.schema.json" },
          "expiresAt": { "type": "string", "format": "date-time" },
          "saved": { "const": false }
        }
      },
      "Error": {
        "type": "object", "additionalProperties": false, "required": ["error"],
        "properties": {
          "error": {
            "type": "object", "additionalProperties": false, "required": ["code", "message"],
            "properties": {
              "code": { "type": "string", "enum": ["INVALID_REQUEST", "INVALID_JSON", "INVALID_TOKEN", "INVALID_FORMAT", "UNSUPPORTED_VERSION", "INVALID_STRUCTURE", "INVALID_DESIGN", "HANDOFF_UNAVAILABLE", "METHOD_NOT_ALLOWED", "PAYLOAD_TOO_LARGE", "UNSUPPORTED_MEDIA_TYPE", "UNSUPPORTED_ENCODING", "RATE_LIMITED", "SERVICE_UNAVAILABLE"] },
              "message": { "type": "string" },
              "path": { "type": "string", "description": "JSON Pointer to the affected instance. Semantic checks may identify /project as a whole." },
              "retryAfter": { "type": "integer", "minimum": 1 },
              "issues": {
                "type": "array", "maxItems": 20,
                "items": {
                  "type": "object", "additionalProperties": false, "required": ["code", "path", "message"],
                  "properties": { "code": { "type": "string" }, "path": { "type": "string" }, "message": { "type": "string" }, "property": { "type": "string" } }
                }
              }
            }
          }
        }
      }
    },
    "responses": {
      "InvalidRequest": {
        "description": "Malformed UTF-8 JSON, invalid token, or interrupted request.",
        "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } }
      },
      "MethodNotAllowed": {
        "description": "Only POST is supported.",
        "headers": { "Allow": { "schema": { "const": "POST" } } },
        "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } }
      },
      "PayloadTooLarge": {
        "description": "Request exceeds the endpoint's byte limit.",
        "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } }
      },
      "UnsupportedMedia": {
        "description": "Use Content-Type application/json and no compression.",
        "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } }
      },
      "InvalidDesign": {
        "description": "The document failed format, version, schema, or semantic validation. Correct the errors before retrying.",
        "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } }
      },
      "Expired": {
        "description": "No unexpired handoff exists for this token. Prepare a new link.",
        "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } }
      },
      "RateLimited": {
        "description": "Client or global quota exceeded. Wait for Retry-After.",
        "headers": { "Retry-After": { "schema": { "type": "string", "pattern": "^[1-9][0-9]*$" } } },
        "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } }
      },
      "Unavailable": {
        "description": "Storage or server configuration is unavailable. Retry with backoff.",
        "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } }
      }
    }
  }
}
