> ## Documentation Index
> Fetch the complete documentation index at: https://docs.chemolytic.com/llms.txt
> Use this file to discover all available pages before exploring further.

# API predictions

> Send spectra to a deployed model programmatically. Two endpoints: JSON spectra or file upload.

The Chemolytic API exposes every active deployment as two prediction endpoints. Use them to integrate predictions into your LIMS, lab automation, or custom scripts.

<Note>
  API predictions require the **`allow_api_predict`** plan feature. Free plans don't include API access. Upgrade to Pro or higher to enable it.
</Note>

## Endpoints

Both endpoints live under the deployment URL:

| Endpoint                                       | Purpose                     |
| ---------------------------------------------- | --------------------------- |
| `POST /api/.../deployments/{id}/predict/`      | Submit JSON-encoded spectra |
| `POST /api/.../deployments/{id}/predict/file/` | Upload raw CSV files        |

The full URL appears on the **Integration** tab of the deployment detail page.

## Authentication

All requests need an API key in the `Authorization` header:

```
Authorization: Api-Key sk_<your-key>
```

See [API keys](/production/api-keys) for how to create one.

## JSON endpoint

Use this when you can pre-parse spectra into arrays of numbers (e.g., from your own data pipeline).

### Request

```http theme={null}
POST /api/organizations/{slug}/projects/{id}/deployments/{id}/predict/
Authorization: Api-Key sk_xxxxxxxxxxxx
Content-Type: application/json
```

```json theme={null}
{
  "spectra": [
    {
      "sample_name": "sample_001",
      "y_values": [0.45, 0.52, 0.48, 0.51, 0.47, ...]
    },
    {
      "sample_name": "sample_002",
      "y_values": [0.42, 0.49, 0.46, 0.50, 0.45, ...]
    }
  ]
}
```

| Field                   | Type             | Required | Notes                                                                 |
| ----------------------- | ---------------- | -------- | --------------------------------------------------------------------- |
| `spectra`               | array            | Yes      | List of spectrum objects                                              |
| `spectra[].sample_name` | string           | Yes      | Identifier returned with the prediction                               |
| `spectra[].y_values`    | array of numbers | Yes      | One value per x-axis point. Length must match the model's input size. |

### Response (200 OK)

```json theme={null}
{
  "predictions": [
    { "sample_name": "sample_001", "value": 12.34 },
    { "sample_name": "sample_002", "value": 11.87 }
  ],
  "model": {
    "id": 42,
    "name": "Brix Predictor",
    "version": 3,
    "target_property": "brix"
  }
}
```

For classification models, `value` is the predicted category as a string.

## File endpoint

Use this when you have raw files exported by your spectrometer and don't want to parse them yourself.

### Request

```http theme={null}
POST /api/organizations/{slug}/projects/{id}/deployments/{id}/predict/file/
Authorization: Api-Key sk_xxxxxxxxxxxx
Content-Type: multipart/form-data
```

Form fields:

| Field       | Type    | Required | Notes                                                     |
| ----------- | ------- | -------- | --------------------------------------------------------- |
| `file`      | file    | Yes      | One or more CSV files. Repeat the field to send multiple. |
| `sensor_id` | integer | Yes      | The sensor that produced these files                      |

### Response

Identical to the JSON endpoint.

## Code samples

The Integration tab on the deployment detail page generates ready-to-use snippets. Examples:

### Python

```python theme={null}
import requests

ENDPOINT = "https://api.chemolytic.com/api/organizations/your-org/projects/12/deployments/7/predict/"
API_KEY = "sk_xxxxxxxxxxxx"

response = requests.post(
    ENDPOINT,
    headers={"Authorization": f"Api-Key {API_KEY}"},
    json={
        "spectra": [
            {"sample_name": "sample_001", "y_values": [0.45, 0.52, 0.48, ...]}
        ]
    },
)
response.raise_for_status()
print(response.json())
```

### JavaScript

```javascript theme={null}
const response = await fetch(ENDPOINT, {
  method: "POST",
  headers: {
    "Authorization": `Api-Key ${API_KEY}`,
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
    spectra: [
      { sample_name: "sample_001", y_values: [0.45, 0.52, 0.48] }
    ],
  }),
});
const data = await response.json();
```

### cURL (JSON)

```bash theme={null}
curl -X POST "$ENDPOINT" \
  -H "Authorization: Api-Key $API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"spectra":[{"sample_name":"sample_001","y_values":[0.45,0.52,0.48]}]}'
```

### cURL (file upload)

```bash theme={null}
curl -X POST "$ENDPOINT/file/" \
  -H "Authorization: Api-Key $API_KEY" \
  -F "file=@sample_001.csv" \
  -F "file=@sample_002.csv" \
  -F "sensor_id=14"
```

## Limits

| Limit                   | Value                                                   |
| ----------------------- | ------------------------------------------------------- |
| Max spectra per request | Capped server-side; exceeding returns `400 Bad Request` |
| Per-month quota         | `max_api_predictions_month` (plan-based)                |
| Request timeout         | 30 seconds                                              |

For very large batches, split your spectra across multiple requests.

## Errors

| Code                        | When it happens                                                                                                 |
| --------------------------- | --------------------------------------------------------------------------------------------------------------- |
| `400 Bad Request`           | Missing fields, invalid input, point count mismatch, NaN/Inf values, too many spectra                           |
| `403 Forbidden`             | API key doesn't belong to this project, or not authorized for this deployment                                   |
| `429 Too Many Requests`     | Monthly limit exceeded. Body shows `{"limit", "used", "requested", "remaining"}`                                |
| `504 Gateway Timeout`       | Prediction took longer than 30 seconds. Reduce batch size and retry.                                            |
| `500 Internal Server Error` | Model prediction error. Contact [support@chemolytic.com](mailto:support@chemolytic.com) with the deployment ID. |

### Example 400 errors

```json theme={null}
{ "detail": "Spectrum 'sample_001' has 500 points, expected 512." }
{ "detail": "Spectrum 'sample_001' contains NaN or Inf values." }
{ "detail": "Too many spectra (100). Maximum is 50." }
{ "detail": "Deployment is not active." }
{ "detail": "Model not ready (status=building)." }
```

### Example 429 error

```json theme={null}
{
  "detail": "Monthly prediction limit exceeded.",
  "limit": 1000,
  "used": 950,
  "requested": 60,
  "remaining": 0
}
```

## Logging and usage

Every API prediction is:

* Recorded in **PredictionLog** with the API key, sample count, and timestamp (90-day retention)
* Aggregated daily in **PredictionUsage** for quick quota lookups (kept permanently)

You can see your aggregated usage on the deployment detail page under **API predictions this month**.

## Tips

<Tip>
  **Batch predictions**: send 10-50 spectra per request rather than one at a time. Fewer round-trips, lower latency, less quota waste.
</Tip>

<Tip>
  **Cache the endpoint URL and API key in environment variables**: don't hard-code them in your scripts. Treat the API key like a password.
</Tip>

<Tip>
  **Handle 429 gracefully**: check `remaining` in the response and pause until the quota resets if needed.
</Tip>
