> ## 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 PVsyst project

> Send a PVsyst ZIP export to PlantPredict and get back a project with a prediction for each variant.

PlantPredict can import a PVsyst project directly from the ZIP that PVsyst produces. You send the file, check back until the import finishes, and the project and its predictions are in your account.

There is nothing to confirm along the way. Every variant PlantPredict can build is built, and anything it cannot build is reported back to you with the exact file that caused it.

<Note>
  This is the API equivalent of the PVsyst import in the web application. The web import keeps its review page, where you can resolve missing equipment by selecting or uploading a replacement before creating the project. The API has no review step — see [Why a variant might be skipped](#why-a-variant-might-be-skipped).
</Note>

## Before you start

You need API 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 the same way as any other PlantPredict API call, 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>

## Step 1: send the ZIP

Post the file exactly as PVsyst exported it.

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

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

  with open("Desert Ridge.zip", "rb") as f:
      response = requests.post(
          "https://api.plantpredict.terabase.energy/Project/Import/PVsyst",
          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.zip"));

  const response = await fetch("https://api.plantpredict.terabase.energy/Project/Import/PVsyst", {
    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": 4471,
  "statusUrl": "/Project/Import/PVsyst/4471"
}
```

## Step 2: check back until it finishes

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

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

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

  while True:
      status = requests.get(
          f"https://api.plantpredict.terabase.energy/Project/Import/PVsyst/{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. `projectId` is your new project.    |
| `Failed`     | No project was created. `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": 4471,
  "status": "Completed",
  "originalFileName": "Desert Ridge.zip",
  "projectId": 88213,
  "projectName": "Desert Ridge",
  "summary": "Imported 7 of 9 variants. 2 skipped because files they reference are missing from the ZIP or could not be read.",
  "errorCode": null,
  "error": null,
  "imported": [
    { "variantFile": "Desert Ridge.VC0", "predictionId": 90112 }
  ],
  "skipped": [
    {
      "variantFile": "Desert Ridge.VC3",
      "reason": "References CS7N-695TB-AG.PAN, which could not be used.",
      "blockedBy": [
        {
          "category": "module",
          "fileName": "CS7N-695TB-AG.PAN",
          "problem": "Not included in the ZIP."
        }
      ]
    }
  ],
  "failed": []
}
```

## Why a variant might be skipped

A PVsyst variant points at the module, inverter and weather files it was simulated against. PlantPredict imports a variant when it can account for every one of those files, either from the ZIP itself or by matching a record already in your library.

A variant is skipped when a file it references cannot be used:

<AccordionGroup>
  <Accordion title="The file is not in the ZIP">
    PVsyst does not bundle components that come from its own built-in database, so exports frequently arrive without them. Re-export from PVsyst with the components included, or add the equipment to your PlantPredict library first.
  </Accordion>

  <Accordion title="The file is in the ZIP but cannot be read">
    The file is present but PlantPredict could not parse it. The `problem` field carries the specific reason. Re-sending the same file will not help.
  </Accordion>
</AccordionGroup>

<Warning>
  PlantPredict never substitutes a different file for one your variant names. A close match by filename is not good enough to stand in for the equipment your study actually used, so the variant is skipped and reported instead. This is deliberate: it keeps the imported numbers faithful to your PVsyst results.
</Warning>

Everything that could be imported still is. A partial import is a normal outcome, not an error. Fix what the response names and send the ZIP again.

## When an import fails

`Failed` means no project was created. `errorCode` tells you whether it is something you can fix:

| Code                     | What it means                                                                    |
| ------------------------ | -------------------------------------------------------------------------------- |
| `no-importable-variants` | Not one variant could be imported. `skipped` lists every file that needs fixing. |
| `invalid-zip`            | The upload could not be read as a PVsyst export, or contained no variants.       |
| `import-error`           | Something went wrong on our side. Contact support with the `jobId`.              |

The first two are worth retrying with a corrected export. The third is not.

## 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.
* `imported` is empty until the import finishes, so read `status` first.
