Geosetta API Documentation
Welcome to the Geosetta API Documentation. Our platform offers unparalleled access to a wealth of public geotechnical data, empowering your projects with accurate and comprehensive subsurface information. By becoming a sponsor of Geosetta, you not only contribute to the advancement and accessibility of geotechnical data but also gain exclusive API access. This access allows for seamless integration of our extensive data repository into your systems, facilitating more informed decision-making and innovative solutions in your field. Sponsoring Geosetta is more than just a partnership; it's an opportunity to be at the forefront of geotechnical data utilization and to support the ongoing mission of making this valuable data openly accessible to professionals and researchers worldwide. Join us in this endeavor and unlock the full potential of geotechnical data with Geosetta.
Quick Navigation
DIGGS API
In addition to the endpoints below, Geosetta provides a dedicated DIGGS (Data Interchange for Geotechnical and Geoenvironmental Specialists) API for standardized geotechnical data exchange. The DIGGS API allows you to query, retrieve, and work with borehole data in the industry-standard DIGGS XML format.
Full DIGGS API documentation is available at: https://diggs.geosetta.org/api/docs
π VLM Boring Log Extraction API
The VLM (Vision Language Model) extraction service accepts geotechnical boring log PDFs and returns structured JSON data extracted by a fine-tuned vision language model. The service classifies each page as boring log or not, sends boring log pages to a GPU-powered extraction endpoint, and assembles multi-page results grouped by boring ID.
POST /api/vlm/extract
Authentication: Required β X-API-Key header must be provided.
Request: multipart/form-data
| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
file |
File (PDF) | Yes | β | PDF file to process (max 50MB, max 100 pages) |
output_mode |
string | No | both |
per_page, combined, or both |
Example Request (Python):
import requests
import zipfile
import json
import os
api_key = "your_api_key_here"
pdf_file_path = "boring_log_report.pdf"
# Send PDF for extraction
with open(pdf_file_path, "rb") as f:
response = requests.post(
"https://diggs.geosetta.org/api/vlm/extract",
headers={"X-API-Key": api_key},
files={"file": f},
data={
"output_mode": "both"
}
)
if response.status_code == 200:
# Save and extract the zip file
with open("results.zip", "wb") as f:
f.write(response.content)
with zipfile.ZipFile("results.zip", "r") as z:
z.extractall("results")
# Read the combined boring data
for boring_dir in os.listdir("results/borings"):
if boring_dir == "grouping.json":
continue
data_path = f"results/borings/{boring_dir}/{boring_dir}.json"
if os.path.exists(data_path):
with open(data_path) as f:
data = json.load(f)
info = data.get("point_info", [{}])[0]
print(f"{boring_dir}: {info.get('location', 'Unknown')}")
else:
print(f"Error: {response.status_code}")
print(response.text)
Example Request (cURL):
curl -X POST "https://diggs.geosetta.org/api/vlm/extract" \
-H "X-API-Key: YOUR_API_KEY" \
-F "file=@boring_log_report.pdf" \
-F "output_mode=both" \
--output results.zip
Response: application/zip file containing structured JSON results.
Zip structure for per_page mode:
pages/
βββ classification.json
βββ page_0/
β βββ B-1.json
βββ page_3/ (non-boring pages are skipped)
β βββ B-2.json
Zip structure for combined mode:
borings/
βββ grouping.json
βββ B-1/
β βββ B-1.json
βββ B-2/
β βββ B-2.json
Zip structure for both mode: Contains both pages/ and borings/ directories.
classification.json example:
{
"total_pages": 63,
"boring_log_pages": [34, 35, 36, 37, 38],
"non_boring_log_pages": [0, 1, 2, 3, 4]
}
grouping.json example:
{
"borings": {
"B-1": {
"pages": [34, 35, 36],
"notes": []
},
"B-2": {
"pages": [37, 38],
"notes": ["page 38 had no boring_id, assigned by continuation"]
}
},
"ungrouped_pages": []
}
B-1.json example:
{
"point_info": [
{
"boring_id": "B-1",
"project_name": "Highway Bridge Replacement",
"location": "Stafford County, Virginia",
"latitude": 38.348151,
"longitude": -77.484592,
"elevation": 145.6,
"drilling_method": "3.25\" HSA w/ SPTs",
"logged_by": "Russell Kanith/HDR",
"date_started": "2017-04-27",
"date_completed": "2017-04-27",
"page_number": 1
}
],
"samples_spt": [ ... ],
"samples_lab": [ ... ],
"lithology": [ ... ],
"groundwater": [ ... ]
}
Error Responses:
| Status Code | Condition |
|---|---|
400 |
Invalid output_mode, non-PDF file, corrupt PDF, PDF too large (>50MB), too many pages (>100) |
401 |
Missing or invalid API key |
500 |
Classifier model failed to load |
Performance Notes:
- First request after idle may take 2-3 minutes (GPU cold start)
- Subsequent requests: ~15-20 seconds per boring log page
- Non-boring-log pages are classified locally and skipped (no GPU cost)
- A 63-page report with 17 boring log pages takes ~6 minutes
GET /api/vlm/health
Authentication: Not required.
Example Request:
curl "https://diggs.geosetta.org/api/vlm/health"
Response:
{
"status": "ok",
"classifier_loaded": true,
"modal_endpoint": "https://ross-cutts--boring-log-vlm-boringlogvlm-extract.modal.run",
"modal_status": "ok"
}
| Field | Values | Description |
|---|---|---|
status |
ok / degraded |
degraded if classifier not loaded |
classifier_loaded |
boolean | Whether the ResNet page classifier is ready |
modal_status |
ok / cold / unreachable |
GPU endpoint status. cold means it will need warm-up time on next request |
π DIGGS Viewer API
Wraps a DIGGS XML file in a self-contained interactive HTML viewer. The returned HTML works completely offline β no server, no dependencies, just open in any browser.
Viewer features:
- Interactive data tables (boreholes, SPT, lithology, lab results, groundwater)
- Boring log visualizations with soil layers and SPT values
- Cross-section views between boreholes
- Charts (SPT depth plots, soil distribution)
- Geotechnical calculations (bearing capacity, settlement, liquefaction)
Also available as a web UI at diggs.geosetta.org/?app=diggs_viewer (no API key required).
POST /api/viewer/wrap
Authentication: Not required.
Request: multipart/form-data
| Parameter | Type | Required | Description |
|---|---|---|---|
file |
File (.xml or .diggs) | Yes | DIGGS XML file (max 50MB, UTF-8 encoded) |
Response: text/html β self-contained HTML viewer downloaded as {filename}_viewer.html
Example Request (Python):
import requests
# Upload a DIGGS XML file and get back a self-contained HTML viewer
with open("my_borings.diggs.xml", "rb") as f:
response = requests.post(
"https://diggs.geosetta.org/api/viewer/wrap",
files={"file": f}
)
if response.status_code == 200:
with open("my_borings_viewer.html", "wb") as f:
f.write(response.content)
print("Viewer saved! Open my_borings_viewer.html in any browser.")
else:
print(f"Error: {response.status_code}")
print(response.json())
Example Request (cURL):
curl -X POST "https://diggs.geosetta.org/api/viewer/wrap" \
-F "file=@my_borings.diggs.xml" \
-o my_borings_viewer.html
Error Responses:
| Status Code | Condition |
|---|---|
400 |
File must be .xml or .diggs |
400 |
File too large (max 50MB) |
400 |
File must be valid UTF-8 encoded XML |
400 |
File does not appear to be a DIGGS XML document |
500 |
Failed to build viewer |
GET /api/viewer/health
Authentication: Not required.
{
"status": "ok",
"src_exists": true,
"plotly_cached": true,
"index_template": true
}
π Geosetta Subsurface Summary API
Generate comprehensive Geosetta Subsurface Summary reports for any user-defined polygon. These reports compile geological data, soil information, nearby boreholes, and ML predictions into a professional PDF format.
Features:
- Professional report format with comprehensive geotechnical data
- Preliminary site characterization with geological and soil data
- ML predictions for SPT N-values and groundwater depth
- Nearby borings (5-mile radius)
- USGS geology and SoilsGrid data integration
- Clear disclaimers emphasizing preliminary nature
- Two output formats: JSON data or PDF (base64 encoded)
import requests
import json
api_key = "your_api_key_here"
# Define your polygon (GeoJSON format)
polygon_geojson = {
"type": "Polygon",
"coordinates": [[
[-78.4300, 38.1150], # Northwest corner
[-78.4250, 38.1150], # Northeast corner
[-78.4250, 38.1100], # Southeast corner
[-78.4300, 38.1100], # Southwest corner
[-78.4300, 38.1150] # Close polygon
]]
}
# Request body
request_body = {
"geojson": json.dumps(polygon_geojson),
"format": "pdf" # Options: "pdf" for PDF report, "json" for raw data
}
# Make the request
response = requests.post(
"https://geosetta.org/web_map/api/generate_site_report/",
json=request_body,
headers={
"Authorization": f"Bearer {api_key}", # Or use "X-API-Key": api_key
"Content-Type": "application/json"
}
)
# Check the response
if response.status_code == 200:
result = response.json()
if result['status'] == 'success':
if request_body['format'] == 'pdf':
# Decode base64 PDF
import base64
pdf_data = base64.b64decode(result['pdf'])
# Save to file
with open('site_assessment_appendix.pdf', 'wb') as f:
f.write(pdf_data)
print(f"Appendix PDF saved! Size: {result['metadata']['size_bytes']} bytes")
print(f"Filename: {result['metadata']['filename']}")
else:
# JSON data format
print("Report data received:")
print(f"Polygon area: {result['data']['polygon']['area']['acres']:.2f} acres")
print(f"Geology: {result['data']['geology']['formation']}")
print(f"Nearby boreholes: {result['data']['boreholes']['total_count']}")
else:
print(f"Error: {response.status_code}")
print(response.json())
Response Format (PDF mode):
{
"status": "success",
"pdf": "JVBERi0xLjQKJcfs...", // Base64 encoded PDF
"metadata": {
"filename": "geosetta_site_assessment_appendix_20241205_143022.pdf",
"size_bytes": 98765,
"generation_time": 6.23,
"report_version": "1.0"
},
"disclaimer": {
"text": "This report is for preliminary planning purposes only...",
"terms_url": "https://geosetta.org/terms",
"must_accept": true
}
}
Response Format (JSON mode):
{
"status": "success",
"data": {
"polygon": {
"centroid": {"lat": 38.1125, "lon": -78.4275},
"area": {"acres": 123.45, "hectares": 49.98, "square_meters": 499776},
"address": "Near Charlottesville, VA 22903",
"elevation_ft": 485.2
},
"geology": {
"formation": "Granite",
"age": "Paleozoic",
"major_components": ["Granite", "Gneiss"],
"usgs_url": "https://ngmdb.usgs.gov/..."
},
"soils": {
"soil_type": "Alfisols",
"wrb_class": "Luvisols",
"properties": {
"clay_content": 25.5,
"sand_content": 35.2,
"organic_carbon": 1.8
},
"usda_soil_survey_url": "https://websoilsurvey.sc.egov.usda.gov/..."
},
"predictions": {
"predictions_by_depth": [
{
"depth_ft": 0,
"spt_n": "10-25",
"soil_description": "Medium dense sandy CLAY"
},
// ... more depth predictions to 50 ft
],
"groundwater": {
"depth_label": "Medium (10-50 ft)",
"deviation_label": "Moderate (5-20 ft variation)"
}
},
"boreholes": {
"total_count": 15,
"closest_distance_miles": 0.8,
"all_boreholes": [
{
"functional_key": "38.1234;-78.4321",
"distance_miles": 0.8,
"provider": "VDOT",
"depth_ft": 45,
"map_url": "https://geosetta.org/web_map/map/#18/38.1234/-78.4321"
}
// ... more boreholes
]
},
"metadata": {
"collection_time": 5.89,
"timestamp": "2024-12-05T14:30:22Z"
}
},
"disclaimer": {
"text": "This data is for preliminary planning only...",
"terms_url": "https://geosetta.org/terms",
"must_accept": true
}
}
Appendix Contents Include:
- Site Location: Area calculations, nearest address, centroid elevation
- Anticipated Geology: USGS formation data, rock types, age
- Anticipated Surficial Soils: SoilsGrid WRB classification, engineering properties
- Predicted Subsurface Profile: SPT N-values and soil descriptions to 50 ft
- Groundwater Predictions: Depth category and seasonal variation
- Nearby Borings: Within 5-mile radius with clickable map links
- Data Sources & References: Complete citations and contact information
API Endpoint to retrieve Historic Data around Radius:
api_key = "your_api_key_here"
# Your payload and API key
json_payload = {
"deliverableType": "Return_Historic_Data_In_Radius",
"data": {
"points": [
{
"latitude": 39.21884440935613,
"longitude": -76.84346423232522,
"radius_m": 1000
}
]
}
}
# Make the request
response = requests.post(
"https://geosetta.org/web_map/api_key/",
json=json_payload, # Directly send the json_payload
headers={
"Authorization": f"Bearer {api_key}" # Set the API key in the Authorization header
}
)
# Check the response
if response.status_code == 200:
print("API request successful:")
print(response.json())
response_data = response.json() # Convert the Response object to a dictionary
points_in_radius = response_data['results']['points_in_radius']
print(points_in_radius)
else:
print("API request failed:")
print(f"Status code: {response.status_code}")
print(response.text)
The server will respond with a JSON object like this:
{'message': 'Data processed successfully.', 'results': {'points_in_radius': {'type': 'FeatureCollection', 'features': [{'type': 'Point', 'coordinates': [-76.8445259, 39.22082777], 'properties': {'content': ' Geosetta History
\n Source: MDOT
(lat,lng): 39.2208278 , -76.8445259 elev ft: 335.20\n Total Depth: 25.0 ft
Available Deliverables:
\n \n Monthly Precipitation History NOAA
\n \n \n \n
\n View Borehole Log with RSLog\n
\n '}}, {'type': 'Point', 'coordinates': [-76.84413791, 39.22100078], 'properties': {'content': ' Geosetta History
\n Source: MDOT
(lat,lng): 39.2210008 , -76.8441379 elev ft: 346.00\n Total Depth: 5.0 ft
Available Deliverables:
\n \n Monthly Precipitation History NOAA
\n \n \n \n
\n View Borehole Log with RSLog\n
\n '}}, {'type': 'Point', 'coordinates': [-76.8453399, 39.22108078], 'properties': {'content': ' Geosetta History
\n Source: MDOT
(lat,lng): 39.2210808 , -76.8453399 elev ft: 338.10\n Total Depth: 20.0 ft
Available Deliverables:
\n \n Monthly Precipitation History NOAA
\n \n \n \n
\n View Borehole Log with RSLog\n
\n '}}]}}, 'disclaimer': 'The information provided by this API has been made available by various DOTs and other public agencies. The predictive models presented herein are based on machine learning tools applied to the data. The predicted subsurface conditions are a probabilistic model and do not depict the actual conditions at the site. No claim as to the accuracy of the data and predictive model is made or implied. No other representation, expressed or implied, is included or intended and no warranty or guarantee is included or intended in any Geosetta data or models. The User understands these limitations and that Geosetta is a tool for planning subsurface investigations and not a substitute for it. In no event shall Geosetta or the Data Owners be liable to the User or another Party for any incidental, consequential, special, exemplary or indirect damages, lost business profits or lost data arising out of or in any way related to the use of information or data obtained from the Geosetta.org website or API.'}
{'type': 'FeatureCollection', 'features': [{'type': 'Point', 'coordinates': [-76.8445259, 39.22082777], 'properties': {'content': ' Geosetta History
\n Source: MDOT
(lat,lng): 39.2208278 , -76.8445259 elev ft: 335.20\n Total Depth: 25.0 ft
Available Deliverables:
\n \n Monthly Precipitation History NOAA
\n \n \n \n
\n View Borehole Log with RSLog\n
\n '}}, {'type': 'Point', 'coordinates': [-76.84413791, 39.22100078], 'properties': {'content': ' Geosetta History
\n Source: MDOT
(lat,lng): 39.2210008 , -76.8441379 elev ft: 346.00\n Total Depth: 5.0 ft
Available Deliverables:
\n \n Monthly Precipitation History NOAA
\n \n \n \n
\n View Borehole Log with RSLog\n
\n '}}, {'type': 'Point', 'coordinates': [-76.8453399, 39.22108078], 'properties': {'content': ' Geosetta History
\n Source: MDOT
(lat,lng): 39.2210808 , -76.8453399 elev ft: 338.10\n Total Depth: 20.0 ft
Available Deliverables:
\n \n Monthly Precipitation History NOAA
\n \n \n \n
\n View Borehole Log with RSLog\n
\n '}}]}
API Endpoint to retrieve data predictions (Note you can predict up to 50 points per request with a maximum depth of 100 feet.):
New Feature: Groundwater Predictions
Each point now includes groundwater depth and seasonal variation predictions powered by machine learning models:
- Depth Categories:
- 0 = Shallow (β€10 ft)
- 1 = Medium (10-50 ft)
- 2 = Deep (>50 ft)
- Seasonal Deviation Categories:
- 0 = Low variation (β€5 ft)
- 1 = Moderate variation (5-20 ft)
- 2 = High variation (>20 ft)
- Probabilities: Confidence percentages for each category (shallow/medium/deep for depth, low/moderate/high for variation)
api_key = "your_api_key_here"
# Your payload and API key
json_payload = {
"deliverableType": "SPT_Point_Prediction",
"data": {
"points": [
{
"latitude": 39.387080,
"longitude": -76.813480,
"depth": 50,
"surfaceelevation": None # Optional: if not provided, will be calculated automatically
},
{
"latitude": 39.4,
"longitude": -76.8,
"depth": 30
# surfaceelevation will be calculated automatically if not provided
}
]
}
}
# Make the request
response = requests.post(
"https://geosetta.org/web_map/api_key/",
json=json_payload, # Directly send the json_payload
headers={
"Authorization": f"Bearer {api_key}" # Set the API key in the Authorization header
}
)
# Check the response
if response.status_code == 200:
print("API request successful:")
print(response.json())
else:
print("API request failed:")
print(f"Status code: {response.status_code}")
print(response.text)
The server will respond with a JSON object like this:
{
"message": "Data processed successfully.",
"results": [
{
"point": {
"latitude": 39.387080,
"longitude": -76.813480,
"depth": 50,
"surfaceelevation": null,
"minimum_distance_from_trained_point_(mi)": 2.13
},
"profiles": [
{
"depth": 0,
"dominant_grain_size": "SAND",
"dominant_N_value": "10-25"
},
{
"depth": 1,
"dominant_grain_size": "SAND",
"dominant_N_value": "10-25"
},
// ... depth profiles continue for each foot up to the requested depth
{
"depth": 50,
"dominant_grain_size": "CLAY",
"dominant_N_value": "26-41"
}
],
"groundwater": {
"depth_category": 1,
"depth_label": "Medium (10-50 ft)",
"depth_probabilities": {
"shallow": 23.7,
"medium": 55.9,
"deep": 20.4
},
"deviation_category": 1,
"deviation_label": "Moderate (5-20 ft variation)",
"deviation_probabilities": {
"low": 44.9,
"moderate": 50.2,
"high": 4.9
}
}
},
{
"point": {
"latitude": 39.4,
"longitude": -76.8,
"depth": 30,
"surfaceelevation": null,
"minimum_distance_from_trained_point_(mi)": 1.87
},
"profiles": [
{
"depth": 0,
"dominant_grain_size": "SILT",
"dominant_N_value": "1-9"
},
{
"depth": 1,
"dominant_grain_size": "SILT",
"dominant_N_value": "10-25"
},
// ... depth profiles continue for each foot up to the requested depth
{
"depth": 30,
"dominant_grain_size": "SAND",
"dominant_N_value": "10-25"
}
],
"groundwater": {
"depth_category": 0,
"depth_label": "Shallow (β€10 ft)",
"depth_probabilities": {
"shallow": 65.2,
"medium": 28.4,
"deep": 6.4
},
"deviation_category": 0,
"deviation_label": "Low (β€5 ft variation)",
"deviation_probabilities": {
"low": 72.1,
"moderate": 21.3,
"high": 6.6
}
}
}
],
"disclaimer": "The information provided by this API has been made available by various DOTs and other public agencies. The predictive models presented herein are based on machine learning tools applied to the data. The predicted subsurface conditions are a probabilistic model and do not depict the actual conditions at the site. No claim as to the accuracy of the data and predictive model is made or implied. No other representation, expressed or implied, is included or intended and no warranty or guarantee is included or intended in any Geosetta data or models. The User understands these limitations and that Geosetta is a tool for planning subsurface investigations and not a substitute for it. In no event shall Geosetta or the Data Owners be liable to the User or another Party for any incidental, consequential, special, exemplary or indirect damages, lost business profits or lost data arising out of or in any way related to the use of information or data obtained from the Geosetta.org website or API."
}
This endpoint generates a link to geosetta that will show provided project data alongside public history in geosetta for visualization.
api_key = "your_api_key_here"
# Your payload and API key
json_payload = {
"deliverableType": "Project_Link",
"data": {
"type": "FeatureCollection",
"features": [
{
"type": "Feature",
"geometry": {
"coordinates": [
[
[
-123.119842,
49.323426
],
[
-123.117503,
49.323421
],
[
-123.117524,
49.322477
],
[
-123.118871,
49.322239
],
[
-123.120083,
49.322294
],
[
-123.119842,
49.323426
]
]
],
"type": "Polygon"
},
"properties": {
"Project Title": "RSLog Example Project #1 (Imperial) ",
"Project Number": "EX20-001",
"Client": "ABC Client Company Ltd."
}
},
{
"type": "Feature",
"geometry": {
"coordinates": [
-123.11768,
49.32323
],
"type": "Point"
},
"properties": {
"Name": "AH20-4a",
"Depth": "13 ft",
"Groundwater Depth": "",
"Project Ref.": "RSLog Example Project #1 (Imperial) (EX20-001)"
}
},
{
"type": "Feature",
"geometry": {
"coordinates": [
-123.11934,
49.32298
],
"type": "Point"
},
"properties": {
"Name": "testy",
"Depth": "1 ft",
"Groundwater Depth": "",
"Project Ref.": "RSLog Example Project #1 (Imperial) (EX20-001)"
}
},
{
"type": "Feature",
"geometry": {
"coordinates": [
-123.11843,
49.3227
],
"type": "Point"
},
"properties": {
"Name": "AH21-01",
"Depth": "38.5 ft",
"Groundwater Depth": "",
"Project Ref.": "RSLog Example Project #1 (Imperial) (EX20-001)"
}
},
{
"type": "Feature",
"geometry": {
"coordinates": [
-123.11978,
49.32242
],
"type": "Point"
},
"properties": {
"Name": "AH20-3",
"Depth": "30 ft",
"Groundwater Depth": "16.2 ft",
"Project Ref.": "RSLog Example Project #1 (Imperial) (EX20-001)"
}
}
]
}
}
# Make the request
response = requests.post(
"https://geosetta.org/web_map/api_key/",
json={
"json_data": json.dumps(json_payload) # Encode the dictionary as a JSON string
},
headers={
"Authorization": f"Bearer {api_key}" # Set the API key in the Authorization header
}
)
# Check the response
if response.status_code == 200:
print("API request successful:")
print(response.json())
else:
print("API request failed:")
print(f"Status code: {response.status_code}")
print(response.text)
The server will respond with a JSON object like this:
{'message': 'Data processed successfully.', 'results': 'https://geosetta.org/web_map/map/jqM826', 'disclaimer': 'The information provided by this API has been made available by various DOTs and other public agencies. The predictive models presented herein are based on machine learning tools applied to the data. The predicted subsurface conditions are a probabilistic model and do not depict the actual conditions at the site. No claim as to the accuracy of the data and predictive model is made or implied. No other representation, expressed or implied, is included or intended and no warranty or guarantee is included or intended in any Geosetta data or models. The User understands these limitations and that Geosetta is a tool for planning subsurface investigations and not a substitute for it. In no event shall Geosetta or the Data Owners be liable to the User or another Party for any incidental, consequential, special, exemplary or indirect damages, lost business profits or lost data arising out of or in any way related to the use of information or data obtained from the Geosetta.org website or API.'}
API endpoint retrieves the distance from a pre-trained point. It's important to note that not all data on Geosetta has been included in the training process. Therefore, the distance provided is relative to the nearest location that has been used in our training dataset.
api_key = "your_api_key_here"
# Your payload and API key
json_payload = {
"deliverableType": "Distance_From_Trained_Point",
"data": {
"points": [
{
"latitude": 39.387080,
"longitude": -76.813480,
"depth": 50
},
{
"latitude": 39.4,
"longitude": -76.8,
"depth": 30
}
]
}
}
# Make the request
response = requests.post(
"https://geosetta.org/web_map/api_key/",
json={
"json_data": json.dumps(json_payload) # Encode the dictionary as a JSON string
},
headers={
"Authorization": f"Bearer {api_key}" # Set the API key in the Authorization header
}
)
# Check the response
if response.status_code == 200:
print("API request successful:")
print(response.json())
else:
print("API request failed:")
print(f"Status code: {response.status_code}")
print(response.text)
The server will respond with a JSON object like this:
{'message': 'Data processed successfully.', 'results': {'minimum_distance_from_trained_point_(mi)': 2.13}, 'disclaimer': 'The information provided by this API has been made available by various DOTs and other public agencies. The predictive models presented herein are based on machine learning tools applied to the data. The predicted subsurface conditions are a probabilistic model and do not depict the actual conditions at the site. No claim as to the accuracy of the data and predictive model is made or implied. No other representation, expressed or implied, is included or intended and no warranty or guarantee is included or intended in any Geosetta data or models. The User understands these limitations and that Geosetta is a tool for planning subsurface investigations and not a substitute for it. In no event shall Geosetta or the Data Owners be liable to the User or another Party for any incidental, consequential, special, exemplary or indirect damages, lost business profits or lost data arising out of or in any way related to the use of information or data obtained from the Geosetta.org website or API.'}
API endpoint to convert a pdf boring log to a DIGGS file.
import requests
import json
api_key = "your_api_key_here"
# Example PDF file path
pdf_file_path = '/content/example_boring_log.pdf'
# Your payload and API key
json_payload = {
"deliverableType": "Pdf_Extraction",
"data": {
}
}
# Make the request
with open(pdf_file_path, 'rb') as pdf_file:
response = requests.post(
"https://geosetta.org/web_map/api_key/",
files={'file': pdf_file},
data={
'json_data': json.dumps(json_payload)
},
headers={
"Authorization": f"Bearer {api_key}" # Set the API key in the Authorization header
}
)
# Check the response
if response.status_code == 200:
print("API request successful:")
# Save the zip file content to a file
zip_file_path = '/content/processed_results.zip'
with open(zip_file_path, 'wb') as zip_file:
zip_file.write(response.content)
print(f"Zip file saved to {zip_file_path}")
else:
print("API request failed:")
print(f"Status code: {response.status_code}")
print(response.text)
The server will respond with a JSON object like this:
API request successful:
Zip file saved to /content/processed_results.zip
π VLM Extraction Issue Reporting API
Report incorrect VLM boring log extractions to help improve model accuracy. When the extraction service misreads values β wrong blow counts, shifted depths, missing layers β submit a report with the page images and extraction data. Reports are stored as training examples and used to fine-tune future model versions.
Issue reports can also be submitted directly from the Boring Log Extractor web interface using the Report Issue button next to each boring.
POST /api/vlm/report
Submit an issue report for an incorrect extraction. Attach the page image(s) for the boring and the per-page extraction JSON that the model produced. Use the same X-API-Key you used for the original extraction request.
Authentication: Required β X-API-Key header must be provided.
Request: multipart/form-data
| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
images |
File(s) (PNG) | Yes | β | One or more page images, named page_{N}.png where N is the page number (max 20MB each) |
extraction_jsons |
string (JSON) | Yes | β | JSON object mapping page numbers to their extraction results: {"5": {...}, "6": {...}} |
description |
string | No | β | Description of what's wrong with the extraction |
boring_id |
string | No | β | Boring ID being reported (e.g. B-1) |
filename |
string | No | β | Original PDF filename |
Example Request (Python):
import requests
import json
api_key = "your_api_key_here"
# Page images and extraction data from a previous /vlm/extract call
page_images = {
5: open("pages/B-1_page_5.png", "rb").read(),
6: open("pages/B-1_page_6.png", "rb").read(),
}
extraction_data = {
"5": {"results": {"point_info": [{"boring_id": "B-1"}], "samples_spt": [...]}},
"6": {"results": {"samples_spt": [...], "lithology": [...]}},
}
# Submit the report
response = requests.post(
"https://diggs.geosetta.org/api/vlm/report",
headers={"X-API-Key": api_key},
files=[
("images", ("page_5.png", page_images[5], "image/png")),
("images", ("page_6.png", page_images[6], "image/png")),
],
data={
"extraction_jsons": json.dumps(extraction_data),
"description": "SPT N-values are shifted down one row β 15 at 5ft should be at 10ft",
"boring_id": "B-1",
"filename": "geotech_report.pdf",
},
)
if response.status_code == 200:
report = response.json()
print(f"Report submitted: {report['report']['report_id']}")
else:
print(f"Error: {response.status_code}")
print(response.text)
Example Request (cURL):
curl -X POST "https://diggs.geosetta.org/api/vlm/report" \
-H "X-API-Key: YOUR_API_KEY" \
-F "images=@pages/page_5.png" \
-F "images=@pages/page_6.png" \
-F 'extraction_jsons={"5": {"results": {}}, "6": {"results": {}}}' \
-F "description=SPT N-values shifted down one row" \
-F "boring_id=B-1" \
-F "filename=geotech_report.pdf"
Response:
{
"status": "created",
"report": {
"report_id": "20260316_022326_f637b054",
"api_key_prefix": "lZnSEWjt...",
"boring_id": "B-1",
"filename": "geotech_report.pdf",
"description": "SPT N-values are shifted down one row β 15 at 5ft should be at 10ft",
"pages": [5, 6],
"has_correction": false,
"created_at": "2026-03-16T02:23:26.804417+00:00"
}
}
Error Responses:
| Status Code | Condition |
|---|---|
400 |
Missing images, invalid extraction_jsons JSON, image too large (>20MB), page number mismatch between images and JSON keys |
401 |
Missing or invalid API key |
403 |
Invalid API key or no credits remaining |
PATCH /api/vlm/reports/{report_id}
Add or update a corrected JSON for a previously submitted report. Only the original submitter (matching API key) can update a report.
Authentication: Required β X-API-Key header must be provided (must match the key used to create the report).
Request: multipart/form-data
| Parameter | Type | Required | Description |
|---|---|---|---|
report_id |
string (path) | Yes | The report ID returned from POST /api/vlm/report |
corrected_json |
string (JSON) | Yes | The corrected extraction data |
Example Request (Python):
import requests
import json
api_key = "your_api_key_here"
report_id = "20260316_022326_f637b054"
corrected = {
"point_info": [{"boring_id": "B-1", "total_depth": "50 ft"}],
"samples_spt": [
{"depth_top": "5.0", "depth_bottom": "6.5", "n_value": "15"},
{"depth_top": "10.0", "depth_bottom": "11.5", "n_value": "22"}
]
}
response = requests.patch(
f"https://diggs.geosetta.org/api/vlm/reports/{report_id}",
headers={"X-API-Key": api_key},
data={"corrected_json": json.dumps(corrected)},
)
if response.status_code == 200:
print("Correction submitted successfully")
else:
print(f"Error: {response.status_code}")
print(response.text)
Example Request (cURL):
curl -X PATCH "https://diggs.geosetta.org/api/vlm/reports/20260316_022326_f637b054" \
-H "X-API-Key: YOUR_API_KEY" \
-F 'corrected_json={"point_info": [{"boring_id": "B-1"}], "samples_spt": [...]}'
Response:
{
"status": "updated",
"report": {
"report_id": "20260316_022326_f637b054",
"api_key_prefix": "lZnSEWjt...",
"boring_id": "B-1",
"filename": "geotech_report.pdf",
"description": "SPT N-values are shifted down one row",
"pages": [5, 6],
"has_correction": true,
"corrected_json": "{...}",
"status": "new",
"created_at": "2026-03-16T02:23:26.804417+00:00"
}
}
Error Responses:
| Status Code | Condition |
|---|---|
401 |
Missing or invalid API key |
404 |
Report not found, or API key does not match the original submitter |
GET /api/vlm/reports/{report_id}
Retrieve a single report including metadata and per-page extraction JSONs.
Authentication: Required β X-API-Key header must be provided.
Example Request (cURL):
curl "https://diggs.geosetta.org/api/vlm/reports/20260316_022326_f637b054" \
-H "X-API-Key: YOUR_API_KEY"
Response:
{
"report_id": "20260316_022326_f637b054",
"api_key_prefix": "lZnSEWjt...",
"boring_id": "B-1",
"filename": "geotech_report.pdf",
"description": "SPT N-values are shifted down one row",
"pages": [5, 6],
"page_extractions": {
"5": "{\"results\": {\"point_info\": [{\"boring_id\": \"B-1\"}]}}",
"6": "{\"results\": {\"samples_spt\": [{\"depth\": \"10\"}]}}"
},
"has_correction": false,
"corrected_json": null,
"status": "new",
"created_at": "2026-03-16T02:23:26.826979+00:00"
}
Response Fields:
| Field | Type | Description |
|---|---|---|
report_id |
string | Unique report identifier |
pages |
array | Page numbers included in this report |
page_extractions |
object | Map of page number β raw extraction JSON string |
has_correction |
boolean | Whether a corrected JSON has been submitted |
corrected_json |
string or null | The corrected data, if submitted via PATCH |
status |
string | new, reviewed, training, or resolved |
Seismic Site Class Prediction (ASCE 7-22)
Predict the ASCE 7-22 seismic site class at any location in the continental US. The endpoint runs Geosetta's ML models to predict soil type and SPT N-values at 1-ft intervals from 0–100 ft, converts to shear wave velocity (Vs) using the Imai & Tonouchi (1982) correlation, computes VS30 via harmonic averaging, and assigns a site class ranging from A (hard rock) to E (soft soil).
Results include lower and upper bound site classes derived from the predicted N-value category ranges, plus a per-depth confidence score based on proximity to training data, geological context, and model agreement.
GET /web_map/api/site_class/<lat>/<lng>/
Authentication: Required — X-API-Key header or session login.
| Parameter | Type | Description |
|---|---|---|
lat |
float | Latitude (decimal degrees) |
lng |
float | Longitude (decimal degrees) |
Example Request (Python):
import requests
response = requests.get(
"https://geosetta.org/web_map/api/site_class/38.307/-82.046/",
headers={"X-API-Key": "your_api_key_here"}
)
data = response.json()
print(f"Site Class: {data['site_class']['lower_bound']} - {data['site_class']['upper_bound']}")
print(f"VS30: {data['vs30_ft_s']['average']} ft/s")
print(f"Confidence: {data['confidence']['average_percent']}%")
Example Request (cURL):
curl -H "X-API-Key: your_api_key_here" \
"https://geosetta.org/web_map/api/site_class/38.307/-82.046/"
Example Response:
{
"status": "success",
"location": {
"latitude": 38.307,
"longitude": -82.046,
"elevation_ft": 892.5,
"formation": "Kanawha Formation",
"soil_type": "Silt loam"
},
"site_class": {
"lower_bound": "D",
"upper_bound": "CD",
"standard": "ASCE 7-22"
},
"vs30_ft_s": {
"lower_bound": 723.45,
"upper_bound": 1102.33,
"average": 912.89
},
"profile": {
"depth_ft": [0, 1, 2, "...", 100],
"soil_type": ["CLAY", "CLAY", "SILT", "..."],
"n_value_category": ["1-9", "1-9", "10-25", "..."],
"n_lower": [1, 1, 10, "..."],
"n_upper": [9, 9, 25, "..."],
"vs_lower_ft_s": [318.37, 318.37, 636.12, "..."],
"vs_upper_ft_s": [637.21, 637.21, 922.45, "..."]
},
"confidence": {
"average_percent": 62,
"per_depth": [65, 64, 63, "..."]
},
"disclaimer": "ML-predicted values for preliminary screening only. Not a substitute for site-specific investigation."
}
Response Fields:
| Field | Description |
|---|---|
site_class.lower_bound |
Site class from lower-bound N-values (conservative) |
site_class.upper_bound |
Site class from upper-bound N-values |
vs30_ft_s |
Time-averaged shear wave velocity over top 100 ft (ft/s) |
profile.depth_ft |
Depth array (0–100 ft at 1 ft intervals) |
profile.soil_type |
Predicted soil type at each depth (CLAY, SAND, SILT, GRAVEL, ROCK) |
profile.n_value_category |
Predicted SPT N-value range at each depth |
profile.vs_lower_ft_s / vs_upper_ft_s |
Shear wave velocity bounds at each depth (Imai & Tonouchi 1982) |
confidence.average_percent |
Overall prediction confidence (0–100) |
Notes:
- Site classes follow ASCE 7-22 with intermediate classes (BC, CD, DE)
- VS30 is computed using harmonic averaging over the full 100 ft profile
- N-to-Vs conversion uses Imai & Tonouchi (1982): Vs = 97 × N0.314 × 3.281 ft/s
- Refusal rule: if predicted N ≥ 50 at any depth, all deeper layers are set to N=100
- Confidence scores factor in distance to nearest training data, data density, geological context, model probability, and tree agreement
Vector Tiles — Borehole Data Streaming
Stream Geosetta's full borehole dataset (~500,000 points) into any mapping application using Mapbox Vector Tiles (MVT). This is the most efficient way to display Geosetta data in your own maps — the client only downloads visible tiles, and the binary protobuf format is far smaller than GeoJSON.
Compatible with: MapLibre GL JS, Mapbox GL JS, Deck.gl, QGIS, ArcGIS Online, Leaflet (via plugins), and any client that supports the MVT specification.
GET /web_map/tiles/{z}/{x}/{y}.pbf
Authentication: Required — API key via query parameter or Authorization header.
URL Pattern:
https://geosetta.org/web_map/tiles/{z}/{x}/{y}.pbf?api_key=YOUR_API_KEY
For map clients that set tile URLs as templates, append ?api_key=YOUR_API_KEY to the URL. Alternatively, pass Authorization: Bearer YOUR_API_KEY in request headers (supported by MapLibre GL JS via transformRequest).
| Parameter | Type | Description |
|---|---|---|
z |
integer (0–22) | Zoom level |
x |
integer | Tile column |
y |
integer | Tile row |
Tile Contents:
Each tile contains a single vector layer named boreholes with the following properties:
| Property | Type | Zoom | Description |
|---|---|---|---|
fk |
string | ≥8 | Functional key (lat;lon) — use to query detailed data |
src |
string | ≥8 | Data provider (e.g. VDOT, MDOT) |
t |
string | ≥8 | trained or untrained |
cnt |
integer | <8 | Cluster point count (low zoom only) |
tcnt |
integer | <8 | Trained point count within cluster (low zoom only) |
At zoom < 8, points are server-side clustered for performance. At zoom ≥ 8, individual borehole points are returned.
Example: MapLibre GL JS
var apiKey = 'YOUR_API_KEY';
// Add Geosetta borehole data as a vector tile source
map.addSource('geosetta-boreholes', {
type: 'vector',
tiles: ['https://geosetta.org/web_map/tiles/{z}/{x}/{y}.pbf?api_key=' + apiKey],
minzoom: 0,
maxzoom: 16
});
// Render individual boreholes (zoom >= 8)
map.addLayer({
id: 'boreholes-points',
type: 'circle',
source: 'geosetta-boreholes',
'source-layer': 'boreholes',
minzoom: 8,
paint: {
'circle-radius': 5,
'circle-color': ['match', ['get', 't'],
'trained', '#2ecc71',
'untrained', '#e67e22',
'#999999'
],
'circle-stroke-width': 1,
'circle-stroke-color': '#ffffff'
}
});
// Render clusters (zoom < 8)
map.addLayer({
id: 'boreholes-clusters',
type: 'circle',
source: 'geosetta-boreholes',
'source-layer': 'boreholes',
maxzoom: 8,
paint: {
'circle-radius': ['step', ['get', 'cnt'],
8, 10, 12, 50, 16, 200, 22
],
'circle-color': '#3498db',
'circle-opacity': 0.7
}
});
// Click handler: fetch detailed data on click
map.on('click', 'boreholes-points', function(e) {
var props = e.features[0].properties;
var fk = props.fk; // e.g. "37.658736;-78.124825"
var parts = fk.split(';');
// Fetch full borehole details from Geosetta
fetch('https://geosetta.org/web_map/get-point-info/?lat=' +
parts[0] + '&lng=' + parts[1])
.then(r => r.json())
.then(data => {
new maplibregl.Popup()
.setLngLat(e.lngLat)
.setHTML('<b>' + props.src + '</b><br>' + fk)
.addTo(map);
});
});
Example: QGIS
Add a vector tile layer via Layer → Add Layer → Add Vector Tile Layer:
URL: https://geosetta.org/web_map/tiles/{z}/{x}/{y}.pbf?api_key=YOUR_API_KEY
Min Zoom: 0
Max Zoom: 16
Example: Python (requests)
import requests
api_key = "your_api_key_here"
# Fetch a single tile (zoom 12, covering central Virginia)
response = requests.get(
"https://geosetta.org/web_map/tiles/12/1153/1585.pbf",
headers={"Authorization": f"Bearer {api_key}"}
)
print(f"Tile size: {len(response.content)} bytes")
print(f"Content-Type: {response.headers['Content-Type']}")
# Parse with mapbox-vector-tile library:
# pip install mapbox-vector-tile
import mapbox_vector_tile
tile_data = mapbox_vector_tile.decode(response.content)
boreholes = tile_data.get('boreholes', {}).get('features', [])
print(f"Boreholes in tile: {len(boreholes)}")
Performance Notes:
- Tiles are cached for 1 hour server-side
- Typical tile size: 1–10 KB (vs. 100+ KB for equivalent GeoJSON)
- CORS headers included — works from any origin
- API key required — pass via
?api_key=orAuthorization: Bearerheader - Tiles include both trained and untrained boreholes
Combining with Other Geosetta APIs:
Vector tiles provide the spatial index — use the fk (functional key) from tile features to query detailed data via other Geosetta endpoints:
- Borehole details:
GET /web_map/get-point-info/?lat={lat}&lng={lng} - DIGGS XML:
GET /web_map/DIGGS/{fk} - Borehole log:
GET /web_map/rslog/{fk} - Predictions at location:
GET /web_map/BoreholePred/{lat}/{lon}