Direct Marketing Tariff Upload API Documentation

Overview

The /tariff-management/direct-marketing/upload endpoint allows clients to upload direct marketing tariff data in JSON format. This endpoint supports both single offer uploads and batch uploads of multiple offers.

The system uses capacity tiers defined at the configuration level. Each capacity tier creates a separate offer in the database.


Understanding the Structure

Before uploading pricing data, it's important to understand how offers, configurations, and price matrices work together.

Visual Hierarchy

┌─────────────────────────────────────────────────────────────┐
│ OFFER: "Solar Direct Marketing 100-250kW"                  │
│                                                             │
│  ┌───────────────────────────────────────────────────────┐ │
│  │ CONFIGURATION                                         │ │
│  │  - Technology: Solar                                  │ │
│  │  - Marketing Type: EEG                                │ │
│  │  - Enumeration: Spot                                  │ │
│  │  - Fee Type: Relative                                 │ │
│  │  - Capacity Tier: 100-250kW                           │ │
│  └───────────────────────────────────────────────────────┘ │
│                                                             │
│  ┌───────────────────────────────────────────────────────┐ │
│  │ PRICE MATRIX (Array of Contracts)                    │ │
│  │                                                       │ │
│  │  Contract 1: Start 2026M02, Tenor 11M  ─────┐        │ │
│  │  Contract 2: Start 2026M02, Tenor 1Y   ─────┤        │ │
│  │  Contract 3: Start 2026M02, Tenor 2Y   ─────┤        │ │
│  │  Contract 4: Start 2026M02, Tenor 3Y   ─────┤        │ │
│  │  Contract 5: Start 2026M03, Tenor 10M  ─────┤        │ │
│  │  ...                                         │        │ │
│  │  Contract 23: Start 2028, Tenor 1Y     ─────┘        │ │
│  │                                                       │ │
│  │  ⚠️  All different tenors and start dates            │ │
│  │     must be in THIS array                            │ │
│  └───────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────┘

Key Concepts

IMPORTANT: All contract tenors and start dates for a configuration must be within the same offer's priceMatrix array, even if they have different runtimes (Month, Quarter, Year).

You cannot:

You can:


Endpoint Details


Request Format

The endpoint accepts a JSON file upload with the following structure:

Simplified Example

This basic example shows how to structure multiple contract durations under one configuration:

{
  "offer": {
    "name": "Solar EEG Spot - Direct Marketing",
    "tariffType": 3,
    "countryCode": "DE",
    "description": "Basic example with multiple tenors",
    "configuration": {
      "technology": "Solar",
      "directMarketingType": "EEG",
      "enumerationType": "Spot",
      "serviceFeeType": "Relative",
      "capacityTiers": [
        { "min": 100, "max": 250 }
      ]
    },
    "fees": {
      "guaranteeOfOriginFeeEurPerMWh": 0.5,
      "basicFeePerYear": 1000
    },
    "priceMatrix": [
      {
        "start": "2026M02",
        "tenor": "11M",
        "prices": {
          "2026M02": { "marketValuePercent": 10.0, "variableFixedFeeEurPerMWh": 50.0 },
          "2026M03": { "marketValuePercent": 10.0, "variableFixedFeeEurPerMWh": 50.0 },
          "2026M04": { "marketValuePercent": 10.0, "variableFixedFeeEurPerMWh": 50.0 },
          "2026M05": { "marketValuePercent": 10.0, "variableFixedFeeEurPerMWh": 50.0 },
          "2026M06": { "marketValuePercent": 10.0, "variableFixedFeeEurPerMWh": 50.0 },
          "2026M07": { "marketValuePercent": 10.0, "variableFixedFeeEurPerMWh": 50.0 },
          "2026M08": { "marketValuePercent": 10.0, "variableFixedFeeEurPerMWh": 50.0 },
          "2026M09": { "marketValuePercent": 10.0, "variableFixedFeeEurPerMWh": 50.0 },
          "2026M10": { "marketValuePercent": 10.0, "variableFixedFeeEurPerMWh": 50.0 },
          "2026M11": { "marketValuePercent": 10.0, "variableFixedFeeEurPerMWh": 50.0 },
          "2026M12": { "marketValuePercent": 10.0, "variableFixedFeeEurPerMWh": 50.0 }
        }
      },
      {
        "start": "2026M02",
        "tenor": "1Y",
        "prices": {
          "2026": { "marketValuePercent": 10.0, "variableFixedFeeEurPerMWh": 50.0 }
        }
      },
      {
        "start": "2026M02",
        "tenor": "3Y",
        "prices": {
          "2026": { "marketValuePercent": 10.0, "variableFixedFeeEurPerMWh": 50.0 },
          "2027": { "marketValuePercent": 10.0, "variableFixedFeeEurPerMWh": 50.0 },
          "2028": { "marketValuePercent": 10.0, "variableFixedFeeEurPerMWh": 50.0 }
        }
      }
    ],
    "otherPriceComponents": {
      "basicFeePerYear": "None"
    }
  }
}

What this example shows:

Real-World Example

This example demonstrates a comprehensive pricing structure with multiple start dates and tenors (23 total contracts):

{
  "offer": {
    "name": "Marktprämie - Geförderte Direktvermarktung - Spotmarkt - 100-250 kW",
    "tariffType": 3,
    "countryCode": "DE",
    "description": "",
    "configuration": {
      "technology": "Solar",
      "directMarketingType": "EEG",
      "capacityTiers": [
        { "min": 100, "max": 250 }
      ],
      "enumerationType": "Spot",
      "serviceFeeType": "Relative"
    },
    "fees": {
      "guaranteeOfOriginFeeEurPerMWh": 0.5,
      "basicFeePerYear": 1000
    },
    "priceMatrix": [
      // Monthly rolling contracts (Feb-Jun 2026)
      { "start": "2026M02", "tenor": "11M", "prices": { /* ... */ } },
      { "start": "2026M03", "tenor": "10M", "prices": { /* ... */ } },
      { "start": "2026M04", "tenor": "9M", "prices": { /* ... */ } },
      { "start": "2026M05", "tenor": "8M", "prices": { /* ... */ } },
      { "start": "2026M06", "tenor": "7M", "prices": { /* ... */ } },

      // 1-year contracts starting monthly (Feb-Jun 2026)
      { "start": "2026M02", "tenor": "1Y", "prices": { "2026": { /* ... */ } } },
      { "start": "2026M03", "tenor": "1Y", "prices": { "2026": { /* ... */ } } },
      { "start": "2026M04", "tenor": "1Y", "prices": { "2026": { /* ... */ } } },
      { "start": "2026M05", "tenor": "1Y", "prices": { "2026": { /* ... */ } } },
      { "start": "2026M06", "tenor": "1Y", "prices": { "2026": { /* ... */ } } },

      // 2-year contracts starting monthly (Feb-Jun 2026)
      { "start": "2026M02", "tenor": "2Y", "prices": { "2026": { /* ... */ }, "2027": { /* ... */ } } },
      { "start": "2026M03", "tenor": "2Y", "prices": { "2026": { /* ... */ }, "2027": { /* ... */ } } },
      { "start": "2026M04", "tenor": "2Y", "prices": { "2026": { /* ... */ }, "2027": { /* ... */ } } },
      { "start": "2026M05", "tenor": "2Y", "prices": { "2026": { /* ... */ }, "2027": { /* ... */ } } },
      { "start": "2026M06", "tenor": "2Y", "prices": { "2026": { /* ... */ }, "2027": { /* ... */ } } },

      // 3-year contracts starting monthly (Feb-May 2026)
      { "start": "2026M02", "tenor": "3Y", "prices": { "2026": { /* ... */ }, "2027": { /* ... */ }, "2028": { /* ... */ } } },
      { "start": "2026M03", "tenor": "3Y", "prices": { "2026": { /* ... */ }, "2027": { /* ... */ }, "2028": { /* ... */ } } },
      { "start": "2026M04", "tenor": "3Y", "prices": { "2026": { /* ... */ }, "2027": { /* ... */ }, "2028": { /* ... */ } } },
      { "start": "2026M05", "tenor": "3Y", "prices": { "2026": { /* ... */ }, "2027": { /* ... */ }, "2028": { /* ... */ } } },

      // Yearly contracts starting in calendar years
      { "start": "2027", "tenor": "1Y", "prices": { "2027": { /* ... */ } } },
      { "start": "2027", "tenor": "2Y", "prices": { "2027": { /* ... */ }, "2028": { /* ... */ } } },
      { "start": "2028", "tenor": "1Y", "prices": { "2028": { /* ... */ } } }
    ],
    "otherPriceComponents": {
      "basicFeePerYear": "None"
    }
  }
}

What this pattern provides:

Batch Upload

You can upload multiple offers at once using the offers array:

{
  "offers": [
    {
      "name": "Solar EEG Spot 100-250kW",
      "tariffType": 3,
      "configuration": { /* ... */ },
      "priceMatrix": [ /* ... */ ]
    },
    {
      "name": "Solar EEG Spot 251-500kW",
      "tariffType": 3,
      "configuration": { /* ... */ },
      "priceMatrix": [ /* ... */ ]
    }
  ]
}

Recommended Approach: Create separate offers for each capacity tier (one tier per offer) for clarity and easier maintenance.


Field Descriptions

Root Level Fields

Field Type Required Description
offer object Yes* Single offer object (for single upload)
offers array Yes* Array of offer objects (for batch upload)

*Either offer or offers must be provided, but not both.


Offer Object Fields

Field Type Required Description
name string Yes Name of the tariff offer
tariffType integer Yes Must be 3 for direct marketing
countryCode string Yes ISO country code (e.g., "DE")
description string No Optional description of the offer
configuration object Yes Technology and marketing type configuration
fees object Yes Fee structure
priceMatrix array Yes Array of contract periods with pricing
otherPriceComponents object Yes Additional price components

Configuration Object

Field Type Required Description
technology string Yes Technology type: "Solar" or "Wind"
directMarketingType string Yes Marketing type: "EEG" or "Other"
enumerationType string Yes Enumeration type: "Spot" or "MarketValue"
serviceFeeType string Yes Service fee type: "Relative" or "Absolute"
capacityTiers array No Array of capacity range objects. Recommended: Use one tier per offer for clarity.

Capacity Tiers

Each capacity tier object in the capacityTiers array has:

Field Type Required Description
min decimal No Minimum capacity in kW (default: 0)
max decimal No Maximum capacity in kW. Use null for unlimited upper bound (e.g., ">1000kW")

Recommended Approach:

Create separate offers for each capacity tier:

// Offer 1
{
  "name": "Solar Direct Marketing 100-250kW",
  "configuration": {
    "capacityTiers": [{ "min": 100, "max": 250 }]
  }
}

// Offer 2
{
  "name": "Solar Direct Marketing 251-500kW",
  "configuration": {
    "capacityTiers": [{ "min": 251, "max": 500 }]
  }
}

Benefits:

Note: While multiple capacity tiers in one offer is supported, single tier per offer is clearer and the recommended approach.


Fees Object

Field Type Required Description
guaranteeOfOriginFeeEurPerMWh decimal No Guarantee of origin fee per MWh in EUR
basicFeePerYear decimal No Basic annual fee in EUR

Price Matrix Structure

⚠️ CRITICAL RULE: All tenors for a configuration must be defined in the same offer's priceMatrix array, even if they have different runtimes (Month, Quarter, Year).

The priceMatrix is an array of contract objects. Each contract defines pricing for a specific time period:

"priceMatrix": [
  {
    "start": "2026M02",         // Start period
    "tenor": "11M",             // Contract duration
    "prices": {
      "2026M02": {              // Monthly price keys for monthly tenor
        "marketValuePercent": 10.0,
        "variableFixedFeeEurPerMWh": 50.0
      },
      "2026M03": { /* ... */ }
    }
  },
  {
    "start": "2026M02",         // Same start, different tenor
    "tenor": "1Y",              // Yearly tenor
    "prices": {
      "2026": {                 // Yearly price key for yearly tenor
        "marketValuePercent": 10.0,
        "variableFixedFeeEurPerMWh": 50.0
      }
    }
  }
]

Contract Object Fields

Field Type Required Description
start string Yes Start period. See format table below.
tenor string Yes Contract duration. See tenor format table below.
prices object Yes Pricing schedule with period keys as keys

Start Period Formats

Format Example Description
Monthly "2026M01", "2026M02", ..., "2026M12" Month-specific start (YYYYMNN where NN is 01-12)
Quarterly "2026Q1", "2026Q2", "2026Q3", "2026Q4" Quarter-specific start (YYYYQN where N is 1-4)
Yearly "2026", "2027", "2028" Year-specific start (YYYY)

Tenor Formats

Format Example Description
Monthly "1M", "6M", "11M", "12M" Contract duration in months
Quarterly "1Q", "2Q", "4Q" Contract duration in quarters
Yearly "1Y", "2Y", "3Y", "5Y" Contract duration in years

Price Key Format Rules

The price keys within the prices object must match the tenor format:

Tenor Format Price Keys Must Use
Monthly (e.g., "11M") Monthly keys: "2026M02", "2026M03", etc.
Quarterly (e.g., "4Q") Quarterly keys: "2026Q1", "2026Q2", etc.
Yearly (e.g., "3Y") Yearly keys: "2026", "2027", "2028"

Valid Combinations

Valid:

{
  "start": "2026M02",
  "tenor": "11M",
  "prices": {
    "2026M02": { /* ... */ },
    "2026M03": { /* ... */ }
  }
}

Valid:

{
  "start": "2026M02",
  "tenor": "2Y",
  "prices": {
    "2026": { /* ... */ },
    "2027": { /* ... */ }
  }
}

Invalid - Wrong price key format:

{
  "start": "2026M02",
  "tenor": "11M",
  "prices": {
    "2026": { /* ... */ }  // Should use monthly keys like "2026M02"
  }
}

Price Entry Object

Each entry in the prices object has the period key (year, quarter, or month) and pricing details:

Field Type Required Description
fixedFeeEurPerMWh decimal No Fixed fee per MWh in EUR
marketValuePercent decimal No The percentage fee of the Spot or Market Value revenues generated in a given hour
variableFixedFeeEurPerMWh decimal No Variable fixed fee per MWh in EUR
guaranteeOfOriginFeeEurPerMWh decimal No Guarantee of origin fee per MWh in EUR (overrides fees level)
basicFeePerYear decimal No Basic annual fee in EUR (overrides fees level)

Note: At least one pricing field must be provided per entry.


Other Price Components Object

Field Type Required Description
basicFeePerYear string Yes Additional price component configuration. Use string "None" when no additional components apply, or "Location factor" when location-based pricing is used.

Examples:

// No additional components
"otherPriceComponents": {
  "basicFeePerYear": "None"
}

// With location factor
"otherPriceComponents": {
  "basicFeePerYear": "Location factor"
}

Common Patterns

Here are typical pricing patterns you can implement using the priceMatrix array:

Pattern 1: Rolling Monthly Start Dates with Multiple Durations

Scenario: Offer customers flexibility to start contracts in different months with various durations

Structure:

Example:

"priceMatrix": [
  { "start": "2026M02", "tenor": "11M", "prices": { /* ... */ } },
  { "start": "2026M02", "tenor": "1Y", "prices": { /* ... */ } },
  { "start": "2026M02", "tenor": "3Y", "prices": { /* ... */ } },

  { "start": "2026M03", "tenor": "10M", "prices": { /* ... */ } },
  { "start": "2026M03", "tenor": "1Y", "prices": { /* ... */ } },
  { "start": "2026M03", "tenor": "3Y", "prices": { /* ... */ } },

  // ... continues for Apr, May, Jun
]

Pattern 2: Multiple Year Contracts with Same Start

Scenario: Same start date, different contract lengths

Structure:

Example:

"priceMatrix": [
  {
    "start": "2027",
    "tenor": "1Y",
    "prices": {
      "2027": { "marketValuePercent": 10.0, "variableFixedFeeEurPerMWh": 50.0 }
    }
  },
  {
    "start": "2027",
    "tenor": "2Y",
    "prices": {
      "2027": { "marketValuePercent": 10.0, "variableFixedFeeEurPerMWh": 50.0 },
      "2028": { "marketValuePercent": 10.0, "variableFixedFeeEurPerMWh": 50.0 }
    }
  },
  {
    "start": "2027",
    "tenor": "3Y",
    "prices": {
      "2027": { "marketValuePercent": 10.0, "variableFixedFeeEurPerMWh": 50.0 },
      "2028": { "marketValuePercent": 10.0, "variableFixedFeeEurPerMWh": 50.0 },
      "2029": { "marketValuePercent": 10.0, "variableFixedFeeEurPerMWh": 50.0 }
    }
  }
]

Pattern 3: Monthly Contracts Through Year-End

Scenario: Short-term monthly contracts for remaining months of year

Structure:

Example:

"priceMatrix": [
  { "start": "2026M02", "tenor": "11M", "prices": { /* Feb-Dec */ } },
  { "start": "2026M03", "tenor": "10M", "prices": { /* Mar-Dec */ } },
  { "start": "2026M04", "tenor": "9M", "prices": { /* Apr-Dec */ } },
  { "start": "2026M05", "tenor": "8M", "prices": { /* May-Dec */ } },
  { "start": "2026M06", "tenor": "7M", "prices": { /* Jun-Dec */ } }
]

Enumeration Types

Enumeration Type (Market Type)

Service Fee Type


Response Format

The endpoint returns a DirectMarketingImportResult object:

{
  "success": true,
  "results": [
    {
      "offerId": "guid",
      "success": true,
      "message": "Imported successfully"
    }
  ]
}

Note: When using capacity tiers, multiple results will be returned - one for each capacity tier that created a separate offer.

Response Fields

Field Type Description
success boolean Overall success status (true if all offers imported successfully)
results array Array of import results. Multiple results per JSON offer if capacity tiers are used.
results[].offerId guid ID of the created offer (null if failed)
results[].success boolean Success status for this specific offer
results[].message string Status message for this offer

Example Usage

cURL Example

curl -X POST \
  https://api.example.com/tariff-management/direct-marketing/upload \
  -H "Authorization: Bearer YOUR_TOKEN" \
  -F "data=@tariff_data.json"

JavaScript Example

const formData = new FormData();
formData.append('data', fileInput.files[0]);

fetch('/tariff-management/direct-marketing/upload', {
  method: 'POST',
  headers: {
    'Authorization': 'Bearer YOUR_TOKEN'
  },
  body: formData
})
.then(response => response.json())
.then(data => console.log(data));

PowerShell Example

$filePath = 'C:\path\to\tariff_data.json'
$headers = @{ Authorization = 'Bearer YOUR_TOKEN' }
$form = @{ data = Get-Item $filePath }

Invoke-RestMethod -Uri 'https://api.example.com/tariff-management/direct-marketing/upload' `
  -Method Post `
  -Headers $headers `
  -Form $form

Error Handling

The API returns appropriate HTTP status codes:

Error responses include detailed messages in the results array for each failed offer.


Validation Rules

  1. Capacity Tiers:

    • Each tier must have min < max (if both are specified)
    • Capacity values must be non-negative
    • Capacity overlap between different start periods is allowed
  2. Required Fields: All required fields must be present and non-null

  3. Data Types: All values must match the specified data types

  4. Enumeration Values: Technology, marketing type, and other enums must use valid values

  5. File Format: Must be valid JSON format

  6. Pricing Data: At least one pricing component must be provided per price entry

  7. Unique Tenor Combinations: You cannot upload the same start + tenor combination twice within the same offer. Each priceMatrix entry must be unique.

    • ✅ Valid: {"start": "2026M02", "tenor": "1Y"} and {"start": "2026M03", "tenor": "1Y"}
    • ❌ Invalid: Two entries with {"start": "2026M02", "tenor": "1Y"}
  8. Configuration Grouping: All tenors for a pricing configuration must be within the same offer's priceMatrix array. You cannot split tenors across multiple offers with the same configuration.

    • ✅ Valid: One offer with 23 priceMatrix entries covering all start dates and tenors
    • ❌ Invalid: Multiple offers with same configuration, each with subset of tenors
  9. Price Key Format Consistency:

    • Monthly tenors (e.g., "11M") must use monthly price keys ("2026M02", "2026M03")
    • Quarterly tenors (e.g., "4Q") must use quarterly price keys ("2026Q1", "2026Q2")
    • Yearly tenors (e.g., "1Y", "3Y") must use yearly price keys ("2026", "2027")
  10. Years: Schedule years cannot be in the past


Frequently Asked Questions

Q: Can I mix monthly and yearly tenors in the same priceMatrix?

A: Yes! You can mix different time periods and contract durations within the same offer's priceMatrix array. This is actually a common pattern. See the "Common Patterns" section for examples.


Q: How many priceMatrix entries can one offer have?

A: There is no hard limit, but each entry must be unique (unique start + tenor combination). Real-world examples show 23+ entries in a single offer for comprehensive pricing coverage.


Q: What happens if I upload the same tenor twice for the same start date?

A: The API will reject the upload with a validation error. Each start + tenor combination must be unique within an offer. For example, you cannot have two entries with {"start": "2026M02", "tenor": "1Y"}.


Q: Can I use quarterly periods (2026Q1) instead of monthly (2026M01)?

A: Yes, the API supports quarterly format "YYYYQN" where N is 1-4. Use "2026Q1" for Q1 2026, "2026Q2" for Q2, etc. The same rules apply - quarterly tenors must use quarterly price keys.


Q: Should I create one offer with multiple capacity tiers, or multiple offers with one tier each?

A: The recommended approach is one capacity tier per offer. This makes pricing clearer and easier to manage. For example, create separate offers for "100-250kW", "251-500kW", etc., each with their own priceMatrix.


Q: Can I have different pricing for different start dates in the same offer?

A: Yes! That's the entire purpose of the priceMatrix array. Each entry can have a different start date and different pricing, all under one configuration.


Important Notes on Capacity Tiers

Recommended Approach: One Tier Per Offer

While the API supports multiple capacity tiers in one offer, the recommended approach is to create separate offers for each capacity tier.

Example:

Instead of this:

{
  "offer": {
    "name": "Solar Direct Marketing",
    "configuration": {
      "capacityTiers": [
        { "min": 100, "max": 250 },
        { "min": 251, "max": 500 },
        { "min": 501, "max": 750 }
      ]
    }
  }
}

Do this:

{
  "offers": [
    {
      "name": "Solar Direct Marketing 100-250kW",
      "configuration": {
        "capacityTiers": [{ "min": 100, "max": 250 }]
      }
    },
    {
      "name": "Solar Direct Marketing 251-500kW",
      "configuration": {
        "capacityTiers": [{ "min": 251, "max": 500 }]
      }
    },
    {
      "name": "Solar Direct Marketing 501-750kW",
      "configuration": {
        "capacityTiers": [{ "min": 501, "max": 750 }]
      }
    }
  ]
}

Benefits:

How Capacity Tiers Work

If you do use multiple tiers in one offer, each tier creates a separate offer in the database:

"capacityTiers": [
  { "min": 100, "max": 250 },
  { "min": 251, "max": 500 }
]

Creates:

  1. "Your Offer Name (100-250kW)"
  2. "Your Offer Name (251-500kW)"

Each offer will have all schedules from the priceMatrix but with different capacity ranges at the offer level.

Without Capacity Tiers

If you omit capacityTiers, the system creates 1 offer with no capacity restrictions:

"configuration": {
  "technology": "Wind",
  "directMarketingType": "EEG",
  "enumerationType": "MarketValue",
  "serviceFeeType": "Absolute"
  // No capacityTiers
}

Result: One offer with MinCapacity = null, MaxCapacity = null


Format Detection

The system automatically detects the import type based on the presence of specific fields in the uploaded JSON file. This detection happens at the service layer before processing begins.

Detection Logic

Direct Marketing Format:

PPA Format (Separate format):

The endpoint intelligently routes the import to the correct processing logic based on the detected format.


Notes