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

# Import a PVFARM project

> Upload a PVFARM Shading model file (.pvfshade) and get back a fully built power plant and 3D shade scene on your prediction.

PlantPredict can import a PVFARM project directly from the file PVFARM's **Export Shading model (.pvfshade)** button produces. You send the file, check back until the import finishes, and the power plant and its 3D shade scene are on your prediction.

There is nothing to confirm along the way. PlantPredict creates the module and inverter library records from the file's datasheet values, builds the plant exactly as the file describes it, and builds the shade scene. Anything it cannot import faithfully is reported back to you with a reason instead of being guessed at.

<Note>
  This is the API equivalent of the PVFARM import in the web application. The web import keeps its review flow, where you can upload PAN and OND files for each equipment type and adjust array and transformer values before anything is created. The API has no review step.
</Note>

## Before you start

You need two things: API credentials, and a prediction to import onto.

**Credentials.** Your company administrator generates them once per user: **Manage Account** (the gear icon, bottom left), select the user, then **Generate API Credentials**. Record the Client ID and Client Secret, which are shown only once. Exchange them for an access token using the client credentials grant with your Client ID and Secret sent as a Basic auth header:

```bash theme={null}
curl -X POST https://terabase-prd.auth.us-west-2.amazoncognito.com/oauth2/token \
  -u "$CLIENT_ID:$CLIENT_SECRET" \
  -d "grant_type=client_credentials" \
  -d "scope=transactions/post transactions/get"
```

Every request below sends the resulting token as `Authorization: Bearer`. Tokens last about an hour, and an import can outlive one, so refresh it if a long poll starts returning 401.

<Tip>
  New to the PlantPredict API? The [API Quick Start Guide](/api-docs/api_quick_start_guide) walks through credentials and tokens in more detail.
</Tip>

**A target prediction.** The import lands on an existing prediction, which must:

* be a standard (Block Builder) prediction in one of your projects, and
* already have a weather file selected.

A prediction that already has a built power plant is refused unless you send `overwriteExistingPowerPlant=true` on the import call — your explicit consent to replace the existing plant and its 3D scene. Without it, nothing is ever replaced; importing each design revision onto a fresh prediction keeps your revision history instead.

Create the project and prediction through the API or the website first, and select a weather file on the prediction.

## Step 1: send the file

Post the `.pvfshade` file exactly as PVFARM exported it. An uncompressed `.json` of the same data is also accepted, though PVFARM does not export one. To replace a plant an earlier import built on this prediction, add `?overwriteExistingPowerPlant=true` to the URL (see [When an import fails](#when-an-import-fails)).

<CodeGroup>
  ```bash cURL theme={null}
  curl -X POST "https://api.plantpredict.terabase.energy/Project/$PROJECT_ID/Prediction/$PREDICTION_ID/Import/PVFarm" \
    -H "Authorization: Bearer $TOKEN" \
    -F "file=@Desert Ridge.pvfshade"
  ```

  ```python Python theme={null}
  import requests

  with open("Desert Ridge.pvfshade", "rb") as f:
      response = requests.post(
          f"https://api.plantpredict.terabase.energy/Project/{project_id}/Prediction/{prediction_id}/Import/PVFarm",
          headers={"Authorization": f"Bearer {token}"},
          files={"file": f},
      )

  job_id = response.json()["jobId"]
  ```

  ```javascript Node.js theme={null}
  const form = new FormData();
  form.append("file", fs.createReadStream("Desert Ridge.pvfshade"));

  const response = await fetch(
    `https://api.plantpredict.terabase.energy/Project/${projectId}/Prediction/${predictionId}/Import/PVFarm`,
    {
      method: "POST",
      headers: { Authorization: `Bearer ${token}` },
      body: form,
    },
  );

  const { jobId } = await response.json();
  ```
</CodeGroup>

You get back `202 Accepted` straight away, with the ID you use to follow the import:

```json theme={null}
{
  "jobId": 316,
  "statusUrl": "/Project/Import/PVFarm/316"
}
```

## Step 2: check back until it finishes

The import runs in the background and a large file can take a few minutes. Checking once every 10 seconds is plenty.

<CodeGroup>
  ```bash cURL theme={null}
  curl https://api.plantpredict.terabase.energy/Project/Import/PVFarm/316 \
    -H "Authorization: Bearer $TOKEN"
  ```

  ```python Python theme={null}
  import time

  while True:
      status = requests.get(
          f"https://api.plantpredict.terabase.energy/Project/Import/PVFarm/{job_id}",
          headers={"Authorization": f"Bearer {token}"},
      ).json()

      if status["status"] != "Processing":
          break
      time.sleep(10)

  print(status["summary"])
  ```
</CodeGroup>

`status` is one of three values:

| Status       | Meaning                                                     |
| ------------ | ----------------------------------------------------------- |
| `Processing` | Still running. Check again shortly.                         |
| `Completed`  | Finished. The plant and shade scene are on your prediction. |
| `Failed`     | Nothing was built. `errorCode` says why.                    |

Every response also carries a `summary`: one sentence describing the outcome in plain language, which you can show to a person as-is.

## A finished import

```json theme={null}
{
  "jobId": 316,
  "status": "Completed",
  "originalFileName": "Desert Ridge.pvfshade",
  "projectId": 192183,
  "predictionId": 965830,
  "summary": "Imported a 122.20 MW DC power plant (182,381 modules, 2,379 trackers, 28 inverters) onto prediction 965830 and built its 3D shade scene.",
  "errorCode": null,
  "error": null,
  "plant": {
    "mwDc": 122.1953,
    "modules": 182381,
    "strings": 6289,
    "trackers": 2379,
    "inverters": 28
  },
  "equipment": [
    { "kind": "module", "id": 47413, "name": "PVFARM California PV Module 670W" },
    { "kind": "inverter", "id": 10560, "name": "PVFARM Generic Inverter 3600kW" }
  ],
  "notices": []
}
```

`equipment` lists the library records the import created from the file's datasheet values: one Module per module type and one Inverter per inverter type. Re-importing the same file reuses the same records instead of creating duplicates, and says so in `notices`.

<Warning>
  The generated equipment is built from the datasheet values the file carries, which is lower fidelity than a manufacturer's PAN or OND file. For the highest-fidelity equipment models, import through the website, where you can upload PAN and OND files for each type. PlantPredict never guesses: a file whose module technology it does not recognize fails with a clear error instead of assuming one.
</Warning>

## When an import fails

`Failed` means nothing was built on your prediction. `errorCode` tells you whether it is something you can fix:

| Code                      | What it means                                                                                                                                                                                                  |
| ------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `invalid-file`            | The file could not be used: not a PVFARM shading model file, an old format version, a fixed-tilt project, or a technology PlantPredict does not recognize. Re-export from PVFARM, or use the website's import. |
| `prediction-not-eligible` | The target prediction cannot take the import: no weather file selected, not a Block Builder prediction, or it already has a built power plant and the call did not carry `overwriteExistingPowerPlant=true`.   |
| `import-error`            | Something went wrong on our side. Contact support with the `jobId`.                                                                                                                                            |

The first two are worth retrying after fixing what the message names. The third is not.

<AccordionGroup>
  <Accordion title="The prediction already has a power plant">
    Nothing is replaced without your explicit consent. Either create a new prediction in the same project (a few seconds through the API) and import onto that — each design revision gets its own prediction, keeping your revision history intact — or repeat the call with `overwriteExistingPowerPlant=true` to replace the existing plant and its 3D scene, the same consent the website's import wizard collects through its confirmation dialog.
  </Accordion>

  <Accordion title="The module technology is not recognized">
    PlantPredict maps the file's module technology onto its c-Si or CdTe modeling pathway. A technology string it does not recognize fails the import rather than silently assuming crystalline silicon, because the two pathways model differently. The website's import wizard lets you choose the family interactively.
  </Accordion>

  <Accordion title="The project is fixed-tilt">
    Tracker designs are supported. Fixed-tilt designs are not yet supported and fail with `invalid-file`.
  </Accordion>
</AccordionGroup>

## Notes

* An import is private to the account that created it. A token belonging to a different account cannot see it.
* The `jobId` is worth logging. It is the fastest way for support to find a specific import.
* `plant` and `equipment` are empty until the import finishes, so read `status` first.
* After a completed import, run the prediction as normal; the shade scene the import built contributes near-shading to the results.
