# Endpoints

## **Endpoints**

This section describes all available endpoints in the **METI API v2.0.0**.\
Each endpoint includes authentication requirements, usage details, parameters, and examples.

***

## **Base URL**

All API requests use the following base URL:

```
https://api.millpont.com
```

***

## **Authentication (Required)**

The METI API now uses **Auth0 OAuth2 Client Credentials**.\
You must obtain an access token and include it in every request:

```
Authorization: Bearer <access_token>
```

#### **How to Get a Token**

```bash
curl -X POST "https://<AUTH0_DOMAIN>/oauth/token" \
  -H "Content-Type: application/json" \
  -d '{
    "client_id": "<YOUR_CLIENT_ID>",
    "client_secret": "<YOUR_CLIENT_SECRET>",
    "audience": "https://api.meti.millpont.com",
    "grant_type": "client_credentials"
  }'
```

#### Example Token Response

```json
{
  "access_token": "eyJhbGc...etc",
  "expires_in": 3600,
  "token_type": "Bearer",
  "scope": "read:sources write:sources delete:sources"
}
```

***

## **Account Isolation**

* Every token contains an `account_id`.
* All queries automatically filter to your account.
* Clients **cannot access** other accounts.
* Admin tokens may access all accounts.

***

## **Endpoints Overview**

| Endpoint               | Method | Description                   |
| ---------------------- | ------ | ----------------------------- |
| `/sources`             | GET    | List sources for your account |
| `/sources`             | POST   | Create one or more sources    |
| `/sources/{source_id}` | GET    | Retrieve a single source      |
| `/sources/{source_id}` | DELETE | Delete a single source        |
| `/sources` (query)     | DELETE | Delete by `alt_id`            |

***

## **GET /sources**

#### **List All Sources (Account-Filtered)**

**URL**

```
GET https://api.millpont.com/sources
```

**Headers**

```
Authorization: Bearer <access_token>
```

**Query Parameters (optional)**

| Parameter                | Type    | Description                                                      |
| ------------------------ | ------- | ---------------------------------------------------------------- |
| `methodology`            | string  | Filter by methodology                                            |
| `limit`                  | integer | Pagination size                                                  |
| `offset`                 | integer | Pagination offset                                                |
| `steward_id`             | string  | ID of producer / grower                                          |
| `project_id`             | string  | Group sources by project                                         |
| `outcome_reporting_year` | integer | The calendar year a source's environmental outcomes are tied to. |

#### **Example Request**

```bash
curl -X GET "https://api.millpont.com/sources" \
  -H "Authorization: Bearer <token>"
```

#### **Example Response**

```json
[
  {
    "id": "src_abc123",
    "account_id": "8e05e670-65df-4e7e-b2f1-31e0b3094bcf",
    "methodology": "Agriculture",
    "country": "United States",
    "hectares": 124.5,
    "geojson": { ... }
  }
]
```

***

## **GET /sources/{source\_id}**

Retrieve a single source by its unique ID.

**URL**

```
GET https://api.millpont.com/sources/{source_id}
```

**Optional Parameters Example**\
\
`GET https://api.millpont.com/sources?steward_id=GM-Producer-1`\
\
**Headers**

```
Authorization: Bearer <access_token>
```

**Path Parameter**

| Name        | Type   | Description                           |
| ----------- | ------ | ------------------------------------- |
| `source_id` | string | Source ID (e.g., `src_6ETupIGAbhjb7`) |

#### **Example Request**

```bash
curl -X GET "https://api.millpont.com/sources/src_6ETupIGAbhjb7" \
  -H "Authorization: Bearer <token>"
```

***

## **POST /sources**

#### **Create One or More Sources**

Your request must include:

* A valid **GeoJSON FeatureCollection**
* Each Feature includes:
  * `id`
  * `properties.start_at`
  * `properties.end_at`
  * `geometry` (Polygon or MultiPolygon)
* Optional parameters:
  * `methodology`
  * `tags`
  * `steward_id`
  * `project_id`
  * `outcome_reporting_year`

Note: Methodology (i.e. "Nature Restoration | Production to Conservation") can provide useful context when overlaps arise in the Source ledger. Tags are used to support additional context. A common use case is to denote the function(s) of the area of interest, for example: "production", "processing", "storage", "transportation".  Steward ID allows you to group sources by producer. Project ID is another way to subgroup your sources. Outcome reporting year is the calendar year environmental outcomes are associated with.

The API automatically attaches:

* `account_id` (from JWT)
* `created_by` and `updated_by` (from JWT metadata)

**URL**

```
POST https://api.millpont.com/sources
```

**Headers**

```
Content-Type: application/json
Authorization: Bearer <access_token>
```

***

#### **Example Request**

```bash
curl -X POST "https://api.millpont.com/sources" \
  -H "Authorization: Bearer <token>" \
  -H "Content-Type: application/json" \
  -d '{
    "feature_collection": {
      "type": "FeatureCollection",
      "features": [
        {
          "type": "Feature",
          "id": "APITEST2",
          "properties": {
            "start_at": "2024-11-01T16:25:00.000Z",
            "end_at": "2024-11-10T16:25:00.000Z"
          },
          "geometry": {
            "type": "Polygon",
            "coordinates": [
              [
                [-95.9915183, 32.7766954],
                [-95.9915183, 32.7706437],
                [-95.9849752, 32.7706437],
                [-95.9849752, 32.7766954],
                [-95.9915183, 32.7766954]
              ]
            ]
          }
        }
      ]
    },
    "methodology": "Agriculture",
    "tags": ["test", "api"],
    "steward_id": "GM-Producer-1",
    "project_id": "General Mills",
    "outcome_reporting_year": 2024
  }'
```

#### **Example Response**

```json
[
  {
    "id": "src_6ETupIGAbhjb7",
    "alt_id": "APITEST2",
    "message": "Source created successfully."
  }
]
```

***

## **DELETE /sources/{source\_id}**

Delete a source by its ID.

**URL**

```
DELETE https://api.millpont.com/sources/{source_id}
```

**Headers**

```
Authorization: Bearer <access_token>
```

#### **Example Request**

```bash
curl -X DELETE "https://api.millpont.com/sources/src_tJsijx0UmuGE9" \
  -H "Authorization: Bearer <token>"
```

#### **Example Response**

```json
{
  "message": "Source deleted successfully."
}
```

***

## **DELETE /sources (Delete by alt\_id)**

You may also delete by **alt\_id**:

**URL**

```
DELETE https://api.millpont.com/sources?alt_id=<my_alt_id>
```

**Example**

```bash
curl -X DELETE "https://api.millpont.com/sources?alt_id=APITEST2" \
  -H "Authorization: Bearer <token>"
```

***

## Sandbox

Users want a way to test their polygon data and API scripts without writing anything to the METI database. The sandbox mirrors the real /sources API (same request/response shape, same auth, same validation) but stores data only in in-process memory. This lets them POST a source, get back a fake source\_id,  then do GET /sandbox/sources/{source\_id} to simulate the full round-trip – without any DB side effects.

\
**POST /sandbox/sources**

&#x20;\- Same auth requirements as real POST (write:sources)

&#x20;\- Same SourceCreateRequest body

&#x20;\- Runs all validation: date parsing, convert\_geojson\_to\_wkt() (geometry

&#x20;validation), alt\_id logic, account\_id resolution

&#x20;\- Does NOT touch the DB

&#x20;\- Generates sandbox\_\<uuid> as the source id

&#x20;\- Sets created\_at/updated\_at to datetime.now(timezone.utc)

&#x20;\- Stores result dict in sandbox\_store

&#x20;\- Returns List\[SourceResponse] — identical shape to real POST response

&#x20;\- Spatial analysis fields (conflict, unep\_overlap, h3\_indexes, etc.) return

&#x20;defaults (False/None)

&#x20;**GET /sandbox/sources/{source\_id}**

&#x20;\- Same auth requirements (read:sources)

&#x20;\- Looks up source\_id in sandbox\_store

&#x20;\- Account-filters: returns 404 if entry belongs to different account (unless

&#x20;super\_admin)

&#x20;\- Returns SourceResponse or 404

&#x20;**GET /sandbox/sources**

&#x20;\- Same auth requirements (read:sources)

&#x20;\- Filters sandbox\_store by caller's account\_id (or all if super\_admin)

&#x20;\- Supports: id, alt\_id, methodology, steward\_id, project\_id,

&#x20;outcome\_reporting\_year, limit, offset query params

&#x20;\- Returns List\[SourceResponse]

**DELETE /sandbox/sources/{source\_id} — removes by ID from sandbox\_store, account-filtered**

**DELETE /sandbox/sources?alt\_id=... — removes by alt\_id from sandbox\_store, account-filtered**&#x20;

\
In the sandbox POST handler, sandbox source IDs created look like this:            sandbox\_a3f1c8e2b04d7a916e5f2d3c1b8e0f47                                                      \
\
This is different from the real endpoint, where the id is generated by the database (Postgres auto-generates a UUID via the table default). In the sandbox there's no DB, so the ID is     generated in Python at request time instead.                                                 &#x20;

\
The sandbox\_ prefix is intentional — it makes it immediately obvious in responses that the ID is from the sandbox and won't resolve against the real /sources/{id} endpoint.

<br>
