{
  "openapi": "3.1.0",
  "info": {
    "title": "FormShark read-only Business API",
    "version": "1.0.0",
    "description": "Read-only API for the authenticated key owner. Requires active Business; Free and Pro do not include API access. Obtain a key and the base URL in Account \u2192 Business \u00b7 media and API. Keys can read all owned surveys and responses. Store them only on a trusted server. Replace the placeholder server URL before use. All routes share a limit of 60 requests per minute per owner."
  },
  "servers": [
    {
      "url": "https://example.invalid/surveyApi",
      "description": "Placeholder: replace before use"
    }
  ],
  "security": [
    {
      "bearerAuth": []
    }
  ],
  "components": {
    "securitySchemes": {
      "bearerAuth": {
        "type": "http",
        "scheme": "bearer",
        "description": "Complete API key shown once when created or rotated in Account. Send in Authorization: Bearer <key>. Webhook signing secrets cannot be used here."
      }
    },
    "schemas": {
      "Error": {
        "type": "object",
        "required": [
          "error"
        ],
        "properties": {
          "error": {
            "type": "string"
          }
        },
        "additionalProperties": false
      },
      "Survey": {
        "type": "object",
        "required": [
          "id"
        ],
        "description": "Only id is guaranteed. Optional fields are returned when stored; null values are preserved.",
        "properties": {
          "id": {
            "type": "string"
          },
          "title": {
            "type": [
              "string",
              "null"
            ]
          },
          "owner": {
            "type": [
              "string",
              "null"
            ]
          },
          "createdTimeMillis": {
            "type": [
              "integer",
              "null"
            ]
          },
          "numberOfResponses": {
            "type": [
              "integer",
              "null"
            ]
          },
          "maxResponses": {
            "type": [
              "integer",
              "null"
            ]
          },
          "expiresAtMillis": {
            "type": [
              "integer",
              "null"
            ]
          },
          "isPublic": {
            "type": [
              "boolean",
              "null"
            ]
          },
          "deleted": {
            "type": [
              "boolean",
              "null"
            ]
          },
          "allowAnonymous": {
            "type": [
              "boolean",
              "null"
            ]
          },
          "formFields": {
            "type": [
              "array",
              "null"
            ],
            "items": {
              "type": "object",
              "additionalProperties": true
            },
            "description": "Survey-specific field definitions; interpret at runtime."
          }
        },
        "additionalProperties": true
      },
      "Response": {
        "type": "object",
        "required": [
          "id"
        ],
        "description": "Response fields are returned when stored. Answer structures depend on the survey.",
        "properties": {
          "id": {
            "type": "string"
          },
          "title": {
            "type": [
              "string",
              "null"
            ]
          },
          "formId": {
            "type": [
              "string",
              "null"
            ]
          },
          "reference": {
            "type": [
              "string",
              "null"
            ]
          },
          "startTimeMillis": {
            "type": [
              "integer",
              "null"
            ]
          },
          "submittedTimeMillis": {
            "type": [
              "integer",
              "null"
            ]
          },
          "data": {
            "type": [
              "object",
              "null"
            ],
            "additionalProperties": true,
            "description": "Survey-specific values; may be absent or null."
          },
          "metaData": {
            "type": [
              "object",
              "null"
            ],
            "additionalProperties": true,
            "description": "Survey-specific values; may be absent or null."
          },
          "attachments": {
            "type": [
              "object",
              "null"
            ],
            "description": "Media metadata arrays grouped by field ID. Request /v1/media/{assetId} to obtain a temporary download URL.",
            "additionalProperties": {
              "type": "array",
              "items": {
                "$ref": "#/components/schemas/Attachment"
              }
            }
          }
        },
        "additionalProperties": true
      },
      "Attachment": {
        "type": "object",
        "properties": {
          "assetId": {
            "type": "string"
          },
          "fileName": {
            "type": "string"
          },
          "contentType": {
            "type": "string"
          },
          "sizeBytes": {
            "type": "integer"
          },
          "durationSeconds": {
            "type": "number"
          }
        },
        "additionalProperties": true
      }
    }
  },
  "paths": {
    "/v1/connection": {
      "get": {
        "summary": "Check credentials without survey data",
        "responses": {
          "200": {
            "description": "Success",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "ok"
                  ],
                  "properties": {
                    "ok": {
                      "const": true
                    }
                  }
                },
                "example": {
                  "ok": true
                }
              }
            }
          },
          "401": {
            "description": "Missing, invalid, expired or revoked bearer key (invalid_api_key).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": "invalid_api_key"
                }
              }
            }
          },
          "403": {
            "description": "Active Business coverage required (permission-denied).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "405": {
            "description": "Only GET is supported (method_not_allowed).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "headers": {
              "Allow": {
                "description": "Supported data request method.",
                "schema": {
                  "type": "string",
                  "const": "GET"
                }
              }
            }
          },
          "429": {
            "description": "Owner-wide limit of 60 requests/minute exceeded (rate_limit_exceeded). Wait Retry-After before retrying.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": "rate_limit_exceeded"
                }
              }
            },
            "headers": {
              "Retry-After": {
                "schema": {
                  "type": "integer",
                  "example": 60
                },
                "description": "Seconds to wait"
              }
            }
          },
          "500": {
            "description": "Unexpected server failure (internal_error). Retry with bounded backoff.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "checkConnection"
      }
    },
    "/v1/surveys": {
      "get": {
        "summary": "List owned surveys",
        "responses": {
          "200": {
            "description": "Success",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "data",
                    "nextCursor"
                  ],
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/Survey"
                      }
                    },
                    "nextCursor": {
                      "type": [
                        "string",
                        "null"
                      ],
                      "description": "Opaque cursor for the same owner and route; null means the final page. URL-encode this value unchanged."
                    }
                  }
                },
                "example": {
                  "data": [
                    {
                      "id": "survey_example",
                      "title": "Example survey"
                    }
                  ],
                  "nextCursor": null
                }
              }
            }
          },
          "400": {
            "description": "Invalid parameters (invalid_limit or invalid_cursor for list routes).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Missing, invalid, expired or revoked bearer key (invalid_api_key).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": "invalid_api_key"
                }
              }
            }
          },
          "403": {
            "description": "Active Business coverage required (permission-denied).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Unknown route or item not owned/found (not_found); unavailable media or response (not-found).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "405": {
            "description": "Only GET is supported (method_not_allowed).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "headers": {
              "Allow": {
                "description": "Supported data request method.",
                "schema": {
                  "type": "string",
                  "const": "GET"
                }
              }
            }
          },
          "429": {
            "description": "Owner-wide limit of 60 requests/minute exceeded (rate_limit_exceeded). Wait Retry-After before retrying.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": "rate_limit_exceeded"
                }
              }
            },
            "headers": {
              "Retry-After": {
                "schema": {
                  "type": "integer",
                  "example": 60
                },
                "description": "Seconds to wait"
              }
            }
          },
          "500": {
            "description": "Unexpected server failure (internal_error). Retry with bounded backoff.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "parameters": [
          {
            "name": "limit",
            "in": "query",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 100,
              "default": 50
            }
          },
          {
            "name": "cursor",
            "in": "query",
            "schema": {
              "type": "string",
              "maxLength": 2048
            },
            "description": "Pass the previous nextCursor unchanged for the same owner and route."
          }
        ],
        "operationId": "listSurveys",
        "description": "Ordered by document ID, not time. Default limit 50, maximum 100. Follow nextCursor until null. Pages are not a frozen snapshot."
      }
    },
    "/v1/surveys/{id}": {
      "get": {
        "summary": "Read an owned survey",
        "responses": {
          "200": {
            "description": "Success",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Survey"
                },
                "example": {
                  "id": "survey_example",
                  "title": "Example survey",
                  "formFields": []
                }
              }
            }
          },
          "401": {
            "description": "Missing, invalid, expired or revoked bearer key (invalid_api_key).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": "invalid_api_key"
                }
              }
            }
          },
          "403": {
            "description": "Active Business coverage required (permission-denied).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Unknown route or item not owned/found (not_found); unavailable media or response (not-found).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "405": {
            "description": "Only GET is supported (method_not_allowed).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "headers": {
              "Allow": {
                "description": "Supported data request method.",
                "schema": {
                  "type": "string",
                  "const": "GET"
                }
              }
            }
          },
          "429": {
            "description": "Owner-wide limit of 60 requests/minute exceeded (rate_limit_exceeded). Wait Retry-After before retrying.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": "rate_limit_exceeded"
                }
              }
            },
            "headers": {
              "Retry-After": {
                "schema": {
                  "type": "integer",
                  "example": 60
                },
                "description": "Seconds to wait"
              }
            }
          },
          "500": {
            "description": "Unexpected server failure (internal_error). Retry with bounded backoff.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "pattern": "^[A-Za-z0-9_-]{1,128}$"
            }
          }
        ],
        "operationId": "getSurvey"
      }
    },
    "/v1/surveys/{id}/responses": {
      "get": {
        "summary": "List owned survey responses",
        "responses": {
          "200": {
            "description": "Success",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "data",
                    "nextCursor"
                  ],
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/Response"
                      }
                    },
                    "nextCursor": {
                      "type": [
                        "string",
                        "null"
                      ],
                      "description": "Opaque cursor for the same owner and route; null means the final page. URL-encode this value unchanged."
                    }
                  }
                },
                "example": {
                  "data": [
                    {
                      "id": "response_example",
                      "formId": "survey_example",
                      "data": {
                        "Question": "Example answer"
                      }
                    }
                  ],
                  "nextCursor": null
                }
              }
            }
          },
          "400": {
            "description": "Invalid parameters (invalid_limit or invalid_cursor for list routes).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Missing, invalid, expired or revoked bearer key (invalid_api_key).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": "invalid_api_key"
                }
              }
            }
          },
          "403": {
            "description": "Active Business coverage required (permission-denied).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Unknown route or item not owned/found (not_found); unavailable media or response (not-found).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "405": {
            "description": "Only GET is supported (method_not_allowed).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "headers": {
              "Allow": {
                "description": "Supported data request method.",
                "schema": {
                  "type": "string",
                  "const": "GET"
                }
              }
            }
          },
          "429": {
            "description": "Owner-wide limit of 60 requests/minute exceeded (rate_limit_exceeded). Wait Retry-After before retrying.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": "rate_limit_exceeded"
                }
              }
            },
            "headers": {
              "Retry-After": {
                "schema": {
                  "type": "integer",
                  "example": 60
                },
                "description": "Seconds to wait"
              }
            }
          },
          "500": {
            "description": "Unexpected server failure (internal_error). Retry with bounded backoff.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "pattern": "^[A-Za-z0-9_-]{1,128}$"
            }
          },
          {
            "name": "limit",
            "in": "query",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 100,
              "default": 50
            }
          },
          {
            "name": "cursor",
            "in": "query",
            "schema": {
              "type": "string",
              "maxLength": 2048
            },
            "description": "Pass the previous nextCursor unchanged for the same owner and route."
          }
        ],
        "operationId": "listResponses",
        "description": "Ordered by document ID, not time. Default limit 50, maximum 100. Follow nextCursor until null. Pages are not a frozen snapshot."
      }
    },
    "/v1/media/{assetId}": {
      "get": {
        "summary": "Get temporary media download URL",
        "responses": {
          "200": {
            "description": "Success",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "url",
                    "expiresAt"
                  ],
                  "properties": {
                    "url": {
                      "type": "string",
                      "format": "uri"
                    },
                    "expiresAt": {
                      "type": "integer",
                      "description": "Unix milliseconds"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "Missing, invalid, expired or revoked bearer key (invalid_api_key).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": "invalid_api_key"
                }
              }
            }
          },
          "403": {
            "description": "Active Business coverage required (permission-denied).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Unknown route or item not owned/found (not_found); unavailable media or response (not-found).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "405": {
            "description": "Only GET is supported (method_not_allowed).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "headers": {
              "Allow": {
                "description": "Supported data request method.",
                "schema": {
                  "type": "string",
                  "const": "GET"
                }
              }
            }
          },
          "429": {
            "description": "Owner-wide limit of 60 requests/minute exceeded (rate_limit_exceeded). Wait Retry-After before retrying.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": "rate_limit_exceeded"
                }
              }
            },
            "headers": {
              "Retry-After": {
                "schema": {
                  "type": "integer",
                  "example": 60
                },
                "description": "Seconds to wait"
              }
            }
          },
          "500": {
            "description": "Unexpected server failure (internal_error). Retry with bounded backoff.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "parameters": [
          {
            "name": "assetId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "pattern": "^[A-Za-z0-9_-]{1,128}$"
            }
          }
        ],
        "operationId": "getMediaDownload",
        "description": "Requires committed media and an existing owning response. URL expiry is within five minutes, capped by Business coverage. Treat the URL as a credential. Previously issued URLs remain valid until expiry even after key revocation. Download without forwarding the API bearer key to the storage host."
      }
    }
  }
}
