Skip to main content

LLM Context File

A single-file integration reference designed to be pasted into an AI assistant (Claude, GPT-4, Gemini, etc.) as context before asking it to implement a Qarta API integration.

It contains everything an LLM needs to write correct code on the first try: base URL, exact auth header format, both shape formats, V1/V2 filter differences, all 36 endpoints, output formats, error formats, pagination rules, and a common mistakes table.

How to use it

  1. Download or copy the file content below
  2. Paste it at the start of your prompt, before your implementation request
  3. The LLM will use it as authoritative reference instead of guessing

Example prompt:

<context>
[paste CLAUDE.md content here]
</context>

Using the API documented above, write a Python function that takes a
GeoJSON polygon and returns all portfolio points within it as a DataFrame.

Download

Download CLAUDE.md

File content

Copy the raw markdown below using the button on the top right of the code block.

# Qarta API — Integration Reference

You are implementing an integration against the **Qarta API** (also called the Layers API), a geospatial intelligence platform by Quarticle. This file contains everything you need to implement correctly. Do not guess — use the facts below.

---

## Base URL and Authentication

```
Base URL: https://graph.quarticle.ro
Auth: Authorization: <api_key>
```

**Do NOT add a `Bearer` prefix.** The raw API key goes directly in the `Authorization` header.

```bash
curl https://graph.quarticle.ro/graph/api/v1/places/geocode?q=London \
-H "Authorization: YOUR_API_KEY"
```

---

## The `source` Parameter

All portfolio, entries, points, accumulation, and risk endpoints require a `source` parameter. It identifies which data layer/portfolio to query — a string identifier provisioned to the account (e.g., `"portfolio_01"`).

Discover available sources by calling the provision endpoint first:

```bash
GET /graph/api/v1/reports/portfolio/provision
```

Response structure:
```json
{
"provisioning": {
"portfolio_01": {
"columns": [
{ "field": "insured_value", "type": "number" },
{ "field": "property_type", "type": "string" }
],
"filters": [
{ "field": "oe_id", "label": "Organization", "required": true, "values": ["org_1"] }
],
"reports": { "accumulation": { "enabled": true } }
}
}
}
```

Use the key (`"portfolio_01"`) as the `source` value in all subsequent calls.

---

## Shape Formats

There are **two distinct shape formats** — do not mix them up.

### Polygon shapes (for `/selection` endpoints)

Standard GeoJSON Feature. Coordinates are `[longitude, latitude]` arrays (longitude first).

```json
{
"type": "Feature",
"geometry": {
"type": "Polygon",
"coordinates": [[
[-74.0200, 40.7000],
[-74.0000, 40.7000],
[-74.0000, 40.7300],
[-74.0200, 40.7300],
[-74.0200, 40.7000]
]]
},
"properties": {}
}
```

Rules:
- First and last coordinate must be identical (closes the polygon)
- Minimum 4 coordinate pairs (3 unique + 1 closing)
- `MultiPolygon` is also supported
- Coordinate system: WGS84 / EPSG:4326 only

### Buffer shapes (for `/buffer-selection` and `/buffer` endpoints)

**Not GeoJSON.** Uses a different format with named `{lat, long}` objects. Coordinates are **latitude first** in named fields (opposite order from GeoJSON).

```json
{
"shape": {
"type": "circle",
"coordinates": [{ "lat": 40.7128, "long": -74.0060 }]
},
"radiuses": [1000, 5000, 10000]
}
```

Rules:
- `radiuses` is at the **request body level**, not inside the shape object
- Values are in **meters**
- Multiple radii return one result set per radius
- `coordinates` uses `{ "lat": ..., "long": ... }` — not arrays, not GeoJSON order

---

## Filters: V1 vs V2

### V1 filters — simple flat array

```json
{
"source": "portfolio_01",
"filters": [
{ "field": "oe_id", "value": "my_org" },
{ "field": "property_type", "value": "residential" }
]
}
```

All filters are AND-combined. No nesting.

### V2 filters — recursive nested tree

```json
{
"source": "portfolio_01",
"filters_v2": [
{
"field": "property_type",
"value": "residential",
"filters": [
{ "field": "occupancy", "value": "occupied" }
]
}
]
}
```

### ⚠️ V2 search endpoint exception

The V2 search endpoints (`/graph/api/v2/reports/portfolio/entries/selection/search` and the points equivalent) use `"filters"` as the body key — **not** `"filters_v2"`. All other V2 endpoints use `"filters_v2"`.

---

## All Endpoints

### Location Services (no `source` required)

| Method | Path | Description |
|--------|------|-------------|
| GET | `/graph/api/v1/places/autocomplete?q=` | Address suggestions |
| GET | `/graph/api/v1/places/geocode?q=` | Address → coordinates |
| GET | `/graph/api/v1/places/geocode/reverse?lat=&lon=` | Coordinates → address |

Geocode response:
```json
{ "lat": 51.5034, "lon": -0.1276, "label": "10 Downing St...", "geocodingQuality": "rooftop" }
```

### Portfolio — Provision

| Method | Path | Description |
|--------|------|-------------|
| GET | `/graph/api/v1/reports/portfolio/provision?source=` | Source config, columns, filters |
| GET | `/graph/api/v1/reports/portfolio/provision/filters?source=` | Filter hierarchy |

### Portfolio — V1 Selection (POST, body uses `filters`)

| Method | Path | Description |
|--------|------|-------------|
| POST | `/graph/api/v1/reports/portfolio/points/selection` | GeoJSON points in polygon |
| POST | `/graph/api/v1/reports/portfolio/points/buffer-selection` | GeoJSON points in buffer |
| POST | `/graph/api/v1/reports/portfolio/entries/selection` | Detailed records in polygon |
| POST | `/graph/api/v1/reports/portfolio/entries/buffer-selection` | Detailed records in buffer |
| GET | `/graph/api/v1/reports/portfolio/entries/selection/search` | Search entries (query params) |
| GET | `/graph/api/v1/reports/portfolio/points/attributes?source=&value=` | Attributes for a point |

### Portfolio — V1 Accumulation (POST, body uses `filters`)

| Method | Path | Description |
|--------|------|-------------|
| POST | `/graph/api/v1/reports/portfolio/accumulation/selection` | Aggregate metrics in polygon |
| POST | `/graph/api/v1/reports/portfolio/accumulation/buffer` | Aggregate metrics in buffer |
| POST | `/graph/api/v1/reports/portfolio/accumulation/layer` | Layer-level data retrieval |

### Portfolio — V2 (POST, body uses `filters_v2` except search)

Replace `/v1/` with `/v2/` for all selection and accumulation endpoints above. Search endpoints become POST:

| Method | Path |
|--------|------|
| POST | `/graph/api/v2/reports/portfolio/entries/selection/search` |
| POST | `/graph/api/v2/reports/portfolio/points/selection/search` |

### Risk Reporting

| Method | Path | Description |
|--------|------|-------------|
| POST | `/graph/api/v1/reports/risk/point` | Risk report for a point (PDF/HTML) |
| POST | `/graph/api/v1/reports/portfolio/risk/point-buffer` | Risk report with buffer accumulation |

### Point Enrichment

| Method | Path | Description |
|--------|------|-------------|
| GET | `/graph/featureinfo/risk-lookup/api/v1/bfi/single?lat=&lng=&layers=&operation=` | Single point |
| POST | `/graph/featureinfo/enrichment/api/v1/bfi/batch` | Batch enrichment |

### Web Services (WMS / WFS)

| Method | Path | Key params |
|--------|------|------------|
| GET | `/graph/layers/wms?SERVICE=WMS&REQUEST=GetCapabilities` | Capabilities XML |
| GET | `/graph/layers/wms?SERVICE=WMS&REQUEST=GetMap&...` | Map tile (PNG) |
| GET | `/graph/layers/wms?SERVICE=WMS&REQUEST=GetFeatureInfo&...` | Feature info at pixel |
| GET | `/graph/layers/wms?SERVICE=WMS&REQUEST=GetLegendGraphic&LAYER=` | Legend image |
| GET | `/graph/layers/wms?SERVICE=WMS&REQUEST=DescribeLayer&LAYERS=` | Layer description |
| GET | `/graph/layers/wfs?SERVICE=WFS&REQUEST=GetFeature&TYPENAMES=` | Feature data |

WMS map tiles use **Web Mercator (EPSG:3857)** for the `SRS`/`CRS` and `BBOX` parameters. WFS feature queries use EPSG:4326.

### Live Events

| Method | Path |
|--------|------|
| POST | `/graph/api/v1/live-events/footprints/:footprint/enrich/entries` |

---

## Output Formats

Control the response format with the `Accept` header:

| Accept header | Format | Supported by |
|---------------|--------|--------------|
| `application/json` | JSON (default) | All endpoints |
| `text/csv` | CSV | entries and points endpoints |
| `application/pdf` | PDF | risk report endpoints |
| `text/html` | HTML | risk report endpoints |
| `image/png` | PNG image | WMS GetMap, GetLegendGraphic |
| `application/xml` | XML | WMS/WFS GetCapabilities |

---

## Pagination

Used on entries endpoints with `columns`, `limit`, and `page`:

```json
{
"source": "portfolio_01",
"shape": { ... },
"columns": ["id", "name", "value"],
"limit": 100,
"page": 0
}
```

- `page` is **0-indexed**
- Increment `page` until the response returns fewer items than `limit`

---

## Error Response Formats

Two formats exist — which one you receive depends on the endpoint:

```json
{ "message": "Error description" }
```
```json
{ "errorMessage": { "message": "Error description" } }
```

Handle both. HTTP status codes:

| Code | Meaning |
|------|---------|
| 400 | Invalid parameters or malformed body |
| 401 | Missing or invalid API key |
| 403 | Invalid shape geometry or insufficient permissions |
| 500 | Server error — retry with exponential backoff |

A 403 on a spatial endpoint usually means the GeoJSON shape is invalid (wrong coordinate order, unclosed polygon, wrong CRS).

---

## Python SDK

A fully-typed SDK is available at `sdks/layers/` in this repository (also downloadable as `layers-sdk-python.zip`).

**Requirements:** Python ≥ 3.10, `httpx ≥ 0.27`, `pydantic ≥ 2.0`

**Installation:**
```bash
pip install . # from the unzipped SDK directory
# or
pip install httpx pydantic && # place layers_sdk/ in your project
```

**Client setup:**
```python
from layers_sdk import LayersClient
client = LayersClient(base_url="https://graph.quarticle.ro", api_key="YOUR_API_KEY")
```

**Service map:**

| `client.*` | Covers |
|------------|--------|
| `location` | `autocomplete()`, `geocode()`, `reverse_geocode()` |
| `reports_v1` | V1 portfolio — uses `PortfolioFilter` |
| `reports_v2` | V2 portfolio — uses `RequestFilter` |
| `risk` | `point_risk()`, `point_buffer_risk()`, V2 variants |
| `enrichment` | `enrich_single()`, `enrich_batch()` |
| `wms` | `get_map()`, `get_capabilities()`, `get_feature_info()`, `get_legend_graphic()` |
| `wfs` | `get_feature()` |

**Key model imports:**
```python
from layers_sdk import (
LayersClient,
Feature, Geometry, GeometryType, # polygon shapes
Buffer, Coordinates, # buffer shapes
PortfolioFilter, # V1 filters
RequestFilter, # V2 filters
PointRiskRequestPayload, PointRiskLayers, # risk reports
PointRequest, # batch enrichment
)
from layers_sdk.exceptions import BadRequestError, ForbiddenError, ServerError
```

**Prefer the SDK over raw HTTP** for any Python integration — it handles model validation, serialisation, and raises typed exceptions.

---

## Common Mistakes to Avoid

| Mistake | Correct behaviour |
|---------|-------------------|
| Adding `Bearer ` prefix to the API key | Send the raw key value directly |
| Using `[lat, lon]` coordinate order in GeoJSON | GeoJSON is always `[lon, lat]` |
| Using GeoJSON format for buffer shapes | Buffer shapes use `{type: "circle", coordinates: [{lat, long}]}` |
| Putting `radiuses` inside the shape object | `radiuses` is a top-level request body field |
| Using `filters_v2` in V2 search endpoint body | V2 search uses `filters`, not `filters_v2` |
| Using `GET /v1/status` to test connectivity | That endpoint does not exist — use geocode instead |
| Forgetting to close polygon rings | First and last coordinate must be identical |
| Using UTM or Web Mercator coordinates in shapes | Shapes always use WGS84 / EPSG:4326 |
| Expecting a single error response format | Handle both `{message}` and `{errorMessage: {message}}` |