{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "https://www.layrq.com/layrq-agent-spec.json",
  "title": "LayrQ agent reference",
  "version": "1.0.0",
  "purpose": "Create one schema-constrained spatial layer and its live API from one JSON object.",
  "canonicalBaseUrl": "https://www.layrq.com",
  "rules": {
    "oneGeometryFamilyPerLayer": true,
    "mixedGeometry": false,
    "geometryCollection": false,
    "coordinateReferenceSystem": "EPSG:4326",
    "coordinateOrder": ["longitude", "latitude"],
    "maximumFields": 100,
    "maximumVerticesPerGeometry": 10000,
    "fieldNames": "lowercase snake_case; start with a letter; contain only letters, numbers, and underscores",
    "featureProperties": "Every property must be declared in fields. Required fields must be present and non-null.",
    "polygonValidity": "Polygons must pass PostGIS ST_IsValid. LayrQ returns the validation reason and never silently repairs geometry."
  },
  "layerSpecificationSchema": {
    "type": "object",
    "additionalProperties": false,
    "required": ["name", "title", "geometry", "visibility", "fields"],
    "properties": {
      "name": {
        "type": "string",
        "pattern": "^[a-z][a-z0-9_]{0,62}$",
        "description": "Stable machine name. Use lowercase snake_case."
      },
      "title": {
        "type": "string",
        "minLength": 1,
        "maxLength": 120,
        "description": "Human-readable layer title."
      },
      "geometry": {
        "type": "string",
        "enum": ["point", "line", "polygon"],
        "description": "Geometry family. Singular and matching multipart GeoJSON variants are accepted."
      },
      "visibility": {
        "type": "string",
        "enum": ["public", "private", "unlisted"],
        "description": "Public is internet-readable. Private and unlisted layers require an owner session or API key; unlisted layers are also omitted from public listings."
      },
      "fields": {
        "type": "object",
        "maxProperties": 100,
        "propertyNames": {
          "pattern": "^[a-z][a-z0-9_]{0,62}$"
        },
        "additionalProperties": {
          "oneOf": [
            {
              "type": "object",
              "additionalProperties": false,
              "required": ["type"],
              "properties": {
                "type": {
                  "enum": ["string", "number", "boolean", "date"]
                },
                "required": {
                  "type": "boolean",
                  "default": false
                }
              }
            },
            {
              "type": "object",
              "additionalProperties": false,
              "required": ["type", "values"],
              "properties": {
                "type": {
                  "const": "enum"
                },
                "required": {
                  "type": "boolean",
                  "default": false
                },
                "values": {
                  "type": "array",
                  "minItems": 1,
                  "maxItems": 100,
                  "uniqueItems": true,
                  "items": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 120
                  }
                }
              }
            }
          ]
        }
      }
    }
  },
  "geometryCompatibility": {
    "point": ["Point", "MultiPoint"],
    "line": ["LineString", "MultiLineString"],
    "polygon": ["Polygon", "MultiPolygon"]
  },
  "fieldTypes": {
    "string": {
      "jsonType": "string",
      "example": "Bridge 12"
    },
    "number": {
      "jsonType": "number",
      "example": 98.5
    },
    "boolean": {
      "jsonType": "boolean",
      "example": true
    },
    "date": {
      "jsonType": "string",
      "format": "YYYY-MM-DD",
      "example": "2026-07-29"
    },
    "enum": {
      "jsonType": "string",
      "rule": "Value must exactly match one entry in values.",
      "example": "Good"
    }
  },
  "layerSpecificationExample": {
    "name": "inspection_assets",
    "title": "Inspection Assets",
    "geometry": "line",
    "visibility": "private",
    "fields": {
      "asset_id": {
        "type": "string",
        "required": true
      },
      "condition": {
        "type": "enum",
        "values": ["Good", "Fair", "Poor"]
      },
      "inspection_date": {
        "type": "date"
      },
      "needs_repair": {
        "type": "boolean"
      },
      "length_m": {
        "type": "number"
      }
    }
  },
  "featureInput": {
    "format": "GeoJSON Feature",
    "schema": {
      "type": "object",
      "additionalProperties": false,
      "required": ["type", "geometry", "properties"],
      "properties": {
        "type": {
          "const": "Feature"
        },
        "geometry": {
          "type": "object",
          "required": ["type", "coordinates"],
          "properties": {
            "type": {
              "enum": ["Point", "MultiPoint", "LineString", "MultiLineString", "Polygon", "MultiPolygon"]
            },
            "coordinates": {
              "description": "Valid GeoJSON coordinates in EPSG:4326 longitude-latitude order."
            }
          }
        },
        "properties": {
          "type": "object",
          "description": "Keys and value types must match the layer fields schema."
        }
      }
    },
    "example": {
      "type": "Feature",
      "geometry": {
        "type": "LineString",
        "coordinates": [[-97.7431, 30.2672], [-97.741, 30.269]]
      },
      "properties": {
        "asset_id": "A-1001",
        "condition": "Good",
        "inspection_date": "2026-07-29",
        "needs_repair": false,
        "length_m": 284.6
      }
    }
  },
  "createLayerRequest": {
    "method": "POST",
    "path": "/api/v1/layers",
    "headers": {
      "Content-Type": "application/json",
      "Authorization": "Bearer YOUR_LAYRQ_API_KEY_OR_SUPABASE_ACCESS_TOKEN"
    },
    "body": "One object that validates against layerSpecificationSchema."
  },
  "successfulLayerResponse": {
    "layerId": "lyr_123",
    "featuresUrl": "https://www.layrq.com/api/v1/layers/lyr_123/features",
    "geojsonUrl": "https://www.layrq.com/api/v1/layers/lyr_123.geojson",
    "csvUrl": "https://www.layrq.com/api/v1/layers/lyr_123.csv",
    "tileUrl": "https://www.layrq.com/api/v1/layers/lyr_123/tiles/{z}/{x}/{y}.pbf",
    "schemaUrl": "https://www.layrq.com/api/v1/layers/lyr_123",
    "arcgisServiceUrl": "https://www.layrq.com/arcgis/rest/services/lyr_123/FeatureServer"
  },
  "queryContract": {
    "path": "/api/v1/layers/{layerId}/features",
    "parameters": {
      "bbox": "minLongitude,minLatitude,maxLongitude,maxLatitude",
      "limit": "Default 100; maximum 1000.",
      "cursor": "Opaque nextCursor from the previous response.",
      "fields": "Comma-separated property names.",
      "sort": "A layer field, id, created_at, or updated_at.",
      "direction": "asc or desc. Requires sort and remains bound to the returned cursor.",
      "filter.FIELD": "Equality filter.",
      "filter.FIELD__OPERATOR": "OPERATOR is neq, gt, gte, lt, or lte."
    },
    "paginationRule": "Continue until pagination.hasMore is false. Never invent, decode, or modify the cursor.",
    "example": "/api/v1/layers/lyr_123/features?bbox=-98,29,-96,31&limit=100&fields=asset_id,condition&sort=inspection_date&direction=desc&filter.condition=Good"
  },
  "importContract": {
    "method": "POST",
    "path": "/api/v1/layers/{layerId}/import",
    "contentType": "multipart/form-data",
    "formats": ["CSV", "GeoJSON", "NDJSON", "ZIP shapefile"],
    "fileField": "file",
    "shapefileRule": "Upload a ZIP containing matching .shp, .dbf, and .shx files. Include .prj when coordinates require conversion to EPSG:4326.",
    "fieldMap": {
      "encoding": "JSON object in the multipart fieldMap form field",
      "rule": "Keys are target layer fields. Values are source field names; null skips the target field.",
      "example": {
        "asset_id": "ASSETID",
        "condition": "STATUS",
        "inspection_date": "INSPECT_DT"
      }
    },
    "csvCoordinates": {
      "longitudeField": "Optional custom source longitude column.",
      "latitudeField": "Optional custom source latitude column."
    },
    "batchSize": 500
  },
  "limits": {
    "normalApiResponseFeatures": 1000,
    "defaultGeoJsonFeatures": 1000,
    "maximumSynchronousGeoJsonFeatures": 10000,
    "maximumImportBytes": 209715200,
    "maximumVerticesPerGeometry": 10000,
    "note": "A response that has more data exposes hasMore or truncated metadata and a next cursor. LayrQ never silently truncates."
  },
  "structuredError": {
    "error": {
      "code": "VALIDATION_ERROR",
      "message": "Human-readable explanation",
      "details": {},
      "requestId": "request UUID for support"
    }
  },
  "instructionsForAnAiModel": [
    "Return JSON only unless the user asks for an explanation.",
    "Ask which geometry family to use if it cannot be inferred safely.",
    "Use exactly one of point, line, or polygon for geometry.",
    "Use lowercase snake_case for the layer name and every field name.",
    "Declare every property the user wants to store.",
    "Use enum only when the allowed values are known and finite.",
    "Mark only truly mandatory fields as required.",
    "Choose public, private, or unlisted deliberately; prefer private for sensitive data.",
    "Do not include features, API keys, prose, Markdown, GeometryCollection, or mixed geometry in a layer specification.",
    "Validate the final object against layerSpecificationSchema before returning it."
  ]
}
