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

# Create Invoice with PDF Attachment

> Submit a fully-parsed invoice with all fields pre-filled and attach the original PDF for audit trail—no OCR needed

# Create Invoice with PDF Attachment

Use this guide when **your system already extracts the invoice data** (from an ERP, billing API, EDI feed, or your own OCR pipeline) and you only need to attach the original PDF as a source document for audit purposes.

This is different from the OCR-driven file upload flow (`POST /v1/files/invoices/presigned-urls`), which scans the PDF and uses it to populate the invoice fields. Here, you control all field values and the PDF is treated as read-only evidence.

<Info>
  This guide applies to all invoice types: `electricity`, `heat`, `water`, `recharge`, and `process`.
</Info>

## When to Use This Approach

| Scenario                                                               | Recommended flow            |
| ---------------------------------------------------------------------- | --------------------------- |
| You have an ERP or billing system that exposes structured invoice data | **This guide**              |
| You want to attach a PDF for audit trail but supply all values via API | **This guide**              |
| You are uploading PDFs and want Dcycle to extract the data for you     | Use the Files API OCR flow  |
| You are importing invoices in bulk from a CSV                          | Use the CSV import endpoint |

## Prerequisites

* An active API key (`x-api-key`)
* Your organization UUID (`x-organization-id`)
* The facility UUID(s) where the consumption occurred
* The invoice data already parsed: consumption amount, unit, dates, invoice type
* The PDF file you want to attach

***

## Step 1 — Upload the PDF and Obtain an S3 URL

Call `POST /v1/files/presigned-urls` to register the file and receive a presigned S3 upload URL. This endpoint does **not** trigger OCR or create an invoice skeleton—it simply reserves storage space and returns a URL.

### Request

```bash cURL theme={"theme":{"light":"github-light","dark":"github-dark"}}
curl -X POST "https://api.dcycle.io/v1/files/presigned-urls" \
  -H "x-api-key: ${DCYCLE_API_KEY}" \
  -H "x-organization-id: ${DCYCLE_ORG_ID}" \
  -H "Content-Type: application/json" \
  -d '[
    {
      "name": "invoice_2024_11",
      "extension": "pdf",
      "mime_type": "application/pdf",
      "size_kb": 512
    }
  ]'
```

### Response

```json theme={"theme":{"light":"github-light","dark":"github-dark"}}
[
  {
    "id": "11111111-1111-1111-1111-111111111111",
    "name": "invoice_2024_11",
    "extension": "pdf",
    "mime_type": "application/pdf",
    "size_kb": 512,
    "url": "https://dcycle-files.s3.eu-west-1.amazonaws.com/orgs/a8315ef3/invoice_2024_11.pdf",
    "status": "pending",
    "presigned_url": "https://dcycle-files.s3.eu-west-1.amazonaws.com/orgs/a8315ef3/invoice_2024_11.pdf?X-Amz-Algorithm=AWS4-HMAC-SHA256&..."
  }
]
```

Save the **`url`** field — this is the S3 URL you will pass as `file_url` when creating the invoice.

***

## Step 2 — Upload the File Bytes to S3

`PUT` the raw PDF bytes to the `presigned_url` from Step 1. Use the same `Content-Type` you declared (`application/pdf`).

<CodeGroup>
  ```bash cURL theme={"theme":{"light":"github-light","dark":"github-dark"}}
  curl -X PUT "${PRESIGNED_URL}" \
    -H "Content-Type: application/pdf" \
    --data-binary @invoice_2024_11.pdf
  ```

  ```python Python theme={"theme":{"light":"github-light","dark":"github-dark"}}
  import requests

  with open("invoice_2024_11.pdf", "rb") as f:
      requests.put(
          presigned_url,
          data=f,
          headers={"Content-Type": "application/pdf"},
      )
  ```

  ```javascript JavaScript theme={"theme":{"light":"github-light","dark":"github-dark"}}
  const fs = require('fs');

  await fetch(presignedUrl, {
    method: 'PUT',
    headers: { 'Content-Type': 'application/pdf' },
    body: fs.readFileSync('invoice_2024_11.pdf'),
  });
  ```
</CodeGroup>

A `200` response from S3 confirms the file is stored. No further confirmation call is required for this flow.

***

## Step 3 — Create the Invoice

Call `POST /v1/invoices` with all invoice fields populated and the `url` from Step 1 in the `file_url` field. The PDF is stored as an audit attachment; Dcycle will not re-parse it.

<CodeGroup>
  ```bash cURL - Electricity (single facility) theme={"theme":{"light":"github-light","dark":"github-dark"}}
  curl -X POST "https://api.dcycle.io/v1/invoices" \
    -H "x-api-key: ${DCYCLE_API_KEY}" \
    -H "x-organization-id: ${DCYCLE_ORG_ID}" \
    -H "Content-Type: application/json" \
    -d '{
      "type": "electricity",
      "base_quantity": 15000,
      "unit_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
      "start_date": "2024-11-01",
      "end_date": "2024-11-30",
      "uploaded_by": "b2c3d4e5-f6a7-8901-bcde-fg2345678901",
      "invoice_id": "E-2024-11-001",
      "supplier_id": "c3d4e5f6-g7h8-9012-cdef-gh3456789012",
      "cups": "ES0031406398765432GH0F",
      "file_url": "https://dcycle-files.s3.eu-west-1.amazonaws.com/orgs/a8315ef3/invoice_2024_11.pdf",
      "facility_percentages": [
        {
          "organization_id": "d4e5f6g7-h8i9-0123-defg-hi4567890123",
          "facility_id": "e5f6g7h8-i9j0-1234-efgh-ij5678901234",
          "percentage": 1.0,
          "company_name": "Acme Corp",
          "facility_name": "Madrid Office"
        }
      ]
    }'
  ```

  ```bash cURL - Heat (distributed across facilities) theme={"theme":{"light":"github-light","dark":"github-dark"}}
  curl -X POST "https://api.dcycle.io/v1/invoices" \
    -H "x-api-key: ${DCYCLE_API_KEY}" \
    -H "x-organization-id: ${DCYCLE_ORG_ID}" \
    -H "Content-Type: application/json" \
    -d '{
      "type": "heat",
      "base_quantity": 5000,
      "unit_id": "f6g7h8i9-j0k1-2345-fghi-jk6789012345",
      "start_date": "2024-11-01",
      "end_date": "2024-11-30",
      "uploaded_by": "b2c3d4e5-f6a7-8901-bcde-fg2345678901",
      "invoice_id": "H-2024-11-015",
      "facility_fuel_id": "g7h8i9j0-k1l2-3456-ghij-kl7890123456",
      "file_url": "https://dcycle-files.s3.eu-west-1.amazonaws.com/orgs/a8315ef3/invoice_2024_11.pdf",
      "facility_percentages": [
        {
          "organization_id": "d4e5f6g7-h8i9-0123-defg-hi4567890123",
          "facility_id": "e5f6g7h8-i9j0-1234-efgh-ij5678901234",
          "percentage": 0.6,
          "company_name": "Acme Corp",
          "facility_name": "Building A"
        },
        {
          "organization_id": "d4e5f6g7-h8i9-0123-defg-hi4567890123",
          "facility_id": "h8i9j0k1-l2m3-4567-hijk-lm8901234567",
          "percentage": 0.4,
          "company_name": "Acme Corp",
          "facility_name": "Building B"
        }
      ]
    }'
  ```

  ```python Python theme={"theme":{"light":"github-light","dark":"github-dark"}}
  import os
  import requests

  DCYCLE_API_KEY = os.environ["DCYCLE_API_KEY"]
  DCYCLE_ORG_ID = os.environ["DCYCLE_ORG_ID"]

  headers = {
      "x-api-key": DCYCLE_API_KEY,
      "x-organization-id": DCYCLE_ORG_ID,
      "Content-Type": "application/json",
  }

  # --- Step 1: reserve S3 storage ---
  res = requests.post(
      "https://api.dcycle.io/v1/files/presigned-urls",
      headers=headers,
      json=[{
          "name": "invoice_2024_11",
          "extension": "pdf",
          "mime_type": "application/pdf",
          "size_kb": 512,
      }],
  )
  file_record = res.json()[0]
  s3_url = file_record["url"]
  presigned_url = file_record["presigned_url"]

  # --- Step 2: upload file bytes ---
  with open("invoice_2024_11.pdf", "rb") as f:
      requests.put(presigned_url, data=f, headers={"Content-Type": "application/pdf"})

  # --- Step 3: create the invoice ---
  invoice_payload = {
      "type": "electricity",
      "base_quantity": 15000,
      "unit_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
      "start_date": "2024-11-01",
      "end_date": "2024-11-30",
      "uploaded_by": "b2c3d4e5-f6a7-8901-bcde-fg2345678901",
      "invoice_id": "E-2024-11-001",
      "supplier_id": "c3d4e5f6-g7h8-9012-cdef-gh3456789012",
      "file_url": s3_url,
      "facility_percentages": [
          {
              "organization_id": DCYCLE_ORG_ID,
              "facility_id": "e5f6g7h8-i9j0-1234-efgh-ij5678901234",
              "percentage": 1.0,
              "company_name": "Acme Corp",
              "facility_name": "Madrid Office",
          }
      ],
  }

  invoice_res = requests.post(
      "https://api.dcycle.io/v1/invoices",
      headers=headers,
      json=invoice_payload,
  )
  invoice = invoice_res.json()
  print(f"Invoice created: {invoice['id']} — status: {invoice['status']}")
  ```

  ```javascript JavaScript theme={"theme":{"light":"github-light","dark":"github-dark"}}
  const axios = require('axios');
  const fs = require('fs');

  const headers = {
    'x-api-key': process.env.DCYCLE_API_KEY,
    'x-organization-id': process.env.DCYCLE_ORG_ID,
    'Content-Type': 'application/json',
  };

  // Step 1 — reserve storage
  const { data: [fileRecord] } = await axios.post(
    'https://api.dcycle.io/v1/files/presigned-urls',
    [{ name: 'invoice_2024_11', extension: 'pdf', mime_type: 'application/pdf', size_kb: 512 }],
    { headers },
  );

  // Step 2 — upload file bytes
  await fetch(fileRecord.presigned_url, {
    method: 'PUT',
    headers: { 'Content-Type': 'application/pdf' },
    body: fs.readFileSync('invoice_2024_11.pdf'),
  });

  // Step 3 — create the invoice
  const { data: invoice } = await axios.post(
    'https://api.dcycle.io/v1/invoices',
    {
      type: 'electricity',
      base_quantity: 15000,
      unit_id: 'a1b2c3d4-e5f6-7890-abcd-ef1234567890',
      start_date: '2024-11-01',
      end_date: '2024-11-30',
      uploaded_by: 'b2c3d4e5-f6a7-8901-bcde-fg2345678901',
      invoice_id: 'E-2024-11-001',
      supplier_id: 'c3d4e5f6-g7h8-9012-cdef-gh3456789012',
      file_url: fileRecord.url,
      facility_percentages: [
        {
          organization_id: process.env.DCYCLE_ORG_ID,
          facility_id: 'e5f6g7h8-i9j0-1234-efgh-ij5678901234',
          percentage: 1.0,
          company_name: 'Acme Corp',
          facility_name: 'Madrid Office',
        },
      ],
    },
    { headers },
  );

  console.log(`Invoice created: ${invoice.id} — status: ${invoice.status}`);
  ```
</CodeGroup>

### Successful Response

```json theme={"theme":{"light":"github-light","dark":"github-dark"}}
{
  "id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
  "type": "electricity",
  "status": "loading",
  "facility_id": "e5f6g7h8-i9j0-1234-efgh-ij5678901234",
  "uploaded_by": "b2c3d4e5-f6a7-8901-bcde-fg2345678901",
  "supplier_id": "c3d4e5f6-g7h8-9012-cdef-gh3456789012",
  "invoice_id": "E-2024-11-001",
  "cups": "ES0031406398765432GH0F",
  "enabled": true,
  "self_consumption": false
}
```

***

## Step 4 — Poll Until Active (Optional)

Invoices start in `loading` state while the platform calculates CO2e emissions. Poll `GET /v1/invoices/{id}` until `status` is no longer `loading`.

```python Python theme={"theme":{"light":"github-light","dark":"github-dark"}}
import time

invoice_id = invoice["id"]

while True:
    detail = requests.get(
        f"https://api.dcycle.io/v1/invoices/{invoice_id}",
        headers=headers,
    ).json()

    status = detail["status"]
    if status == "active":
        print(f"Emissions calculated: {detail['co2e']} kg CO2e")
        break
    elif status == "error":
        print(f"Calculation failed: {detail.get('error_messages')}")
        break

    time.sleep(2)
```

### Status Values

| Status     | Meaning                                                         |
| ---------- | --------------------------------------------------------------- |
| `loading`  | Emission calculation is in progress                             |
| `active`   | Calculation complete — `co2e` is populated                      |
| `error`    | Calculation failed — check `error_messages` in the GET response |
| `inactive` | Invoice is disabled and excluded from reports                   |
| `review`   | Invoice is flagged for manual review                            |
| `uploaded` | File received but not yet linked to a facility (OCR flow only)  |

***

## Invoice Type Reference

Different invoice types require different fields. `file_url` is optional but accepted for all types.

| Type          | Required extra field | Scope   |
| ------------- | -------------------- | ------- |
| `electricity` | —                    | Scope 2 |
| `heat`        | `facility_fuel_id`   | Scope 1 |
| `water`       | —                    | Scope 3 |
| `recharge`    | —                    | Scope 1 |
| `process`     | —                    | Scope 1 |

***

## Related Endpoints

<CardGroup cols={2}>
  <Card title="Create Invoice" icon="plus" href="/api-reference/invoices/create">
    Full parameter reference for POST /v1/invoices
  </Card>

  <Card title="Get Invoice" icon="file-invoice" href="/api-reference/invoices/get">
    Retrieve full invoice detail including emissions and status
  </Card>

  <Card title="List Invoices" icon="list" href="/api-reference/invoices/list">
    Query and filter your invoice records
  </Card>

  <Card title="Upload Files" icon="cloud-arrow-up" href="/api-reference/files/upload">
    General-purpose presigned URL file upload reference
  </Card>
</CardGroup>
