Introduction
The /external-offers endpoint is designed to streamline the process of generating offers without the need to access the Happyoffer dashboard. By passing in specific fields, users can easily generate offers through this API. This functionality is particularly useful for automating offer creation and integrating with other systems. All you need to do is ensure that the required fields are included in your request, and the endpoint will handle the rest, generating offers efficiently for your company and any associated child companies.
All the endpoints described on this page require authentication via a Bearer token. Users must include the Bearer token in the request header to access these endpoints. The Bearer token is generated during the authentication process and is associated with the authenticated user. Without a valid Bearer token, requests to these endpoints will be denied.
List all external offers
The external-offers endpoint retrieves all external data sets from a company, including those of any child companies. For example, if Company B is a child of Company A, this endpoint will retrieve all external data sets of both Company A and Company B.
Users need to be authenticated to access this endpoint. The company_id is not required to be passed in the request, as it is derived from the Bearer token generated during authentication. The Bearer token contains the company_id associated with the user.
On a successful request, the endpoint returns a JSON object containing all external data sets associated with the authenticated user's company and any child companies.
https://my-online-offer.com/api/external-offers
The response is a JSON object containing an array of external data sets associated with the authenticated user's company and any child companies. Each data set is represented as an object within the data array, and it includes the following attributes:
id: A unique identifier (UUID) for the data set.company_id: The id of the company to which the data set belongs.offer_template_id: The identifier of the offer template used.external_client_id: The identifier of the external client.client_name: The name of the client.contact_firstname: The first name of the contact person for the client.contact_lastname: The last name of the contact person for the client.contact_title: The title of the contact person for the client (e.g., Mr., Mrs., Ms., Dr., Prof.).contact_email: The email address of the contact person.offer_pdf_url: The URL to the PDF version of the offer.external_user_token: A token representing the external user.external_company_token: A token representing the external company.external_id: (OPTIONAL) The id to maintain a reference between the offer stored in our Happyoffer and your system.draft_url: A URL to open the offer in the test environment.live_url: A URL to open the offer in the live environment.
The tokens will be delivered to you before so you can access the API.
Postman response
{
"data": [
{
"id": "",
"attributes": {
"offer_id": "",
"external_id": "",
"company_id": "",
"offer_template_id": "",
"external_client_id": "",
"client_name": "",
"contact_firstname": "",
"contact_lastname": "",
"contact_title": "",
"contact_email": "",
"offer_pdf_url": "",
"external_user_token": "",
"external_company_token": ""
},
// More records
}]
List one external offer
The external-offers/{id} endpoint retrieves a specific external data set using the provided id. This id corresponds to the unique identifier of an external data set that can be retrieved from the list provided by the external-offers endpoint.
Users need to be authenticated to access this endpoint. The Bearer token generated during authentication must be included in the request header.
Users can only retrieve external data sets from their own company. This is enforced by the company_id attached to the Bearer token used for authentication.
Example response:
{
"data": {
"id": "",
"attributes": {
"offer_id": "",
"external_id": "",
"company_id": "",
"offer_template_id": "",
"external_client_id": "",
"client_name": "",
"contact_firstname": "",
"contact_lastname": "",
"contact_title": "",
"contact_email": "",
"offer_pdf_url": "",
"external_user_token": "",
"external_company_token": ""
}
}
}
Create external offer
The /external-offers endpoint allows authenticated users to create a new external data set. This endpoint requires several fields to be validated and provided in the request body.
Validation rules
The following are the validation rules enforced by the API:
external_id: Optional.offer_template_id: Required and must exist in theofferstable with a valid ID.external_client_id: Required.client_name: Required and must be a string.subject_field_email_button: Nullable.contact_firstname: Required and must be a string.contact_lastname: Required and must be a string.contact_email: Required and must be a valid email address.contact_title: Nullable.offer_pdf_url: Required and must be a valid URL.external_user_token: Required and must be a string.external_company_token: Required and must be a string.products: JSON object containing product details (described below).metadata: JSON object containing metadata.
The JSON structure that needs to be added to the body of the POST request contains all the necessary information to create a new external data set. This JSON object includes the unique identifiers for the offer template and external client, as well as detailed information about the client and their contact person. It also includes links to the PDF of the offer and tokens for user and company authentication. Each field must be validated according to specified rules to ensure the integrity and accuracy of the data.
{
"offer_id": "",
"external_id": "",
"offer_template_id": "",
"external_client_id": "",
"client_name": "",
"contact_firstname": "",
"subject_field_email_button": "",
"contact_lastname": "",
"contact_title": "",
"contact_email": ".com",
"offer_pdf_url": "",
"external_user_token": "",
"external_company_token": "",
"metadata": "",
"products": "See documentation below...",
}
Metadata attribute
This section describes the structure and implementation of dynamic Merge Tags (variables) used for template generation, document assembly, and rich text editors throughout the application.
The core principle is to store the key components of a variable in a database column (metadata) to support dynamic rendering on the frontend and reliable replacement on the backend.
Database structure and storage
The variables are stored in the metadata database column (expected to be a TEXT or VARCHAR field) as a single, valid JSON string. This structure acts as the single source of truth for all available dynamic placeholders.
The JSON object contains a root array key, variables, which holds the configuration for each available tag.
[
{
"variables": [
{
"text": "Contact person first name",
"content": "%%contact_firstname%%"
},
{
"text": "Client company name",
"content": "%%client_company_name%%"
}
]
}
]
Products attribute
This document describes the full structure of the Product Attributes JSON payload used to generate offers.
Each payload represents one full offer, containing general information, product categories, articles, attachments, revision history, and terms & conditions.
- The
clientobject defines who the offer is made for.- This block is mandatory, because the offer cannot be generated without customer details.
- The
generalobject contains overarching information about the offer, including the quotation number, internal references, and description. - The
contactrepresents the sales representative or account manager responsible for the offer. It displays the person’s details inside the offer page and PDF. Categoriesallow you to group articles into logical sections (e.g. “Electrical Work”, “Ventilation”, “Doors”, etc). This groups and structure in the final offer.articlescontain the main line items used to build the offer’s pricing and content. Although optional, they represent the core value of the quote.revisionsdisplays chronological updates, changes or version history inside the offer.terms_and_conditionsrepresents a large block containing legal, pricing, and delivery terms. Supports HTML formatting and is displayed as a full-width content section.attachmentsrepresents external files (PDFs, technical documents, drawings).
How to add alternatives
The parent_article_number field is used to link an article to another article as an alternative option.
When this field is provided, the article will not be shown as a standalone product. Instead, it will appear as an alternative choice to the parent article, accessible through a button on the parent article.
How to group articles per category
The category_id field is used to assign an article to a specific category defined in the categories object. Each article can belong to one category, which determines how and where the article is grouped and displayed in the offer.
How it works
- The value of category_id must match the id of a category defined in the categories array.
- When multiple categories exist, articles are grouped under their respective category headings.
- This helps structure large offers and improves readability for the client.
[
{
"client": {
"client_zip": "3582 BR",
"client_city": "Utrecht",
"client_name": "Test B.V.",
"client_address": "Bisschop Tulpijnendreef 22",
"client_contact_full_name": "mevrouw E. Bakker"
},
"general": {
"offer_number": "TEST_B_V_001",
"customer_number": "1",
"reference_number": "REF-12345",
"offer_description": "Op maat gemaakte offerte in Happyoffer!",
"contact": [
{
"email": "test@email.com",
"full_name": "John Doe",
"image_url": "https://my-online-offer.com/images/voorbeeld-persoon.jpg",
"phone_number": "+3163884053"
}
]
},
"categories" : [
{
"id": 1,
"name": "Zonnepanelen"
},
{
"id": 2,
"name": "Laadpalen"
}
],
"articles": [
{
"name": "BlueBuilt met socket",
"category_id": 2,
"price": 1550,
"amount": 2,
"extra_content": [
{
"name": "Extreem snel",
"content": "Laadt snel op met 11 kW of 22 kW"
}
],
"3d_render": {
"url": null,
"name": null
},
"images": [
{
"name": "Laadpaal van Coolblue",
"url": "https://image.coolblue.nl/max/156xauto/content/e684626c2146dbe3ac632f81e3236c20"
}
],
"documents": [
{
"url": "https://example.com/brochures/laadpaal.pdf"
}
],
"order_column": 1,
"article_number": "LAADPAL-001",
"long_description": "Met een laadpaal pakket krijg je een ‘slimme’ BlueBuilt laadpaal met extra voordelen. Zo houd je je laadpaal bijvoorbeeld online, zodat we je laadpaal regelmatig updaten en je sneller op afstand helpen bij een storing. Daarnaast kijk je gemakkelijk laadsessies terug of verreken je de laadkosten met je werkgever. Hierdoor laad je zorgeloos je auto op. Bij Coolblue kies je uit 2 laadpaal pakketten.",
"short_description": "Onze eigen installateurs komen op korte termijn bij je langs.",
"parent_article_number": null,
"child_article_number": null,
},
{
"name": "[Alternatief] - BlueBuilt met socket",
"category_id": 1,
"alert_text": "Socket model",
"price": 1649,
"amount": 1,
"3d_render": {
"url": null,
"name": null
},
"extra_content": [
{
"name": "Ongekende garantie!",
"content": "Heeft 5 jaar productgarantie"
}
],
"images": [
{
"url": "https://image.coolblue.nl/max/315xauto/content/e684626c2146dbe3ac632f81e3236c20",
"name": "Afbeelding 1"
},
{
"url": "https://image.coolblue.nl/max/315xauto/content/e684626c2146dbe3ac632f81e3236c20",
"name": "Afbeelding 2"
},
],
"documents": [
{
"url": "https://example.com/brochures/laadpaal-alternatief.pdf"
}
],
"order_column": 1,
"article_number": "LAADPAL-002",
"long_description": "Met een laadpaal pakket krijg je een ‘slimme’ BlueBuilt laadpaal met extra voordelen. Zo houd je je laadpaal bijvoorbeeld online, zodat we je laadpaal regelmatig updaten en je sneller op afstand helpen bij een storing. Daarnaast kijk je gemakkelijk laadsessies terug of verreken je de laadkosten met je werkgever. Hierdoor laad je zorgeloos je auto op. Bij Coolblue kies je uit 2 laadpaal pakketten.",
"short_description": "Onze eigen installateurs komen op korte termijn bij je langs.",
"parent_article_number": "LAADPAL-001",
"child_article_number": null
}
],
"article_others": [
{
"alert_text": "Vast laadkabel van 4.5 meter",
"article_number": "LAADPAL-003",
"icon": "heroicon-o-adjustments-vertical",
"name": "BlueBuilt vaste laadkabel",
"short_description": "Op onze laadpalen krijg je 5 jaar productgarantie en 2 jaar installatiegarantie.",
"long_description": "Een laadpaal met load balancing past het laadvermogen aan op de beschikbare hoeveelheid stroom in je huis. Stel, je laadt je auto op en zet tegelijkertijd je wasmachine of oven aan, dan gebruik je op dat moment veel stroom. Om te voorkomen dat je meterkast overbelast raakt of zelfs doorslaat, past het laadpunt hierop aan. De laadsnelheid gaat dan tijdelijk omlaag of zelfs uit. Is de wasmachine weer klaar? Dan laadt je auto weer op volle snelheid verder.",
"price": 1749,
"amount": 1,
"images": [
{
"url": "https://image.coolblue.nl/max/624xauto/content/e03a4073c890ad8dceeabca9558118da",
"name": "Laadpaal met kabel"
}
],
"3d_render": {
"url": null,
"name": null
},
"documents": [
{
"name": "Blusser 6 kg",
"url": "https://example.com/brochures/bekabeld.pdf"
}
],
"order_column": 1,
"parent_article_number": "LAADPAL-001"
}
],
"revisions": [
{
"order_column": 1,
"title": "Specificaties",
"items": [
{
"description": "Beschikbaar in space grey"
},
{
"description": "Socket model."
},
{
"description": "Laadt snel op met 11 kW of 22 kW."
},
{
"description": "Load balancing is mogelijk: past laadsnelheid aan op beschikbare stroom vanuit je huis."
},
{
"description": "Automatische updates van de laadpaal op afstand."
}
]
},
{
"order_column": 2,
"title": "Voordelen",
"items": [
{
"description": "Installatie op korte termijn."
},
{
"description": "Persoonlijk advies."
},
{
"description": "5 jaar productgarantie."
}
]
},
{
"order_column": 3,
"title": "Hoe wij het aanbieden",
"items": [
{
"description": "Particulier laadpaal pakket"
},
{
"description": "Zakelijk laadpaal pakket"
}
]
},
],
"terms_and_conditions": {
"button_url": "https://my-online-offer.com/storage/algemene-voorwaarden.pdf",
"button_text": "Bekijk als PDF",
"name": "Algemene voorwaarden",
"description": "Op al onze aanbiedingen zijn voorwaarden van toepassing.",
"content": "<p>Hier kan je HTML content plaatsen</p>"
},
"attachments": [
{
"order_column": 1,
"name": "Warmtepompen PDF",
"url": "https://www.coolblue.nl/warmtepompen/advies/assortimentspagina-warmtepompen.pdf"
},
{
"order_column": 2,
"name": "Laadpalen PDF",
"url": "https://www.coolblue.nl/warmtepompen/advies/assortimentspagina-warmtepompen"
},
]
}
]
Send offer
This endpoint is used to send an offer email that was previously created but not yet sent. When an offer is created, it is stored but not automatically sent to prevent accidental email triggering. This endpoint explicitly triggers the email sending process for a specified offer.
It supports optional email functionality such as adding CC recipients. Once triggered, the system dispatches the offer via email, along with any CC’d addresses if provided.
Path Parameters
offer_id (integer, required): The unique identifier of the offer to be sent.
Body
-
cc_emails (array of strings, optional):
- A list of email addresses to be included as CC (carbon copy) recipients. This field is optional. If provided, the system will include these email addresses in the CC field of the sent email.
- Must be an array.
- Each item in the array must be a valid email address.
- The field is nullable—omit it entirely if no CCs are needed.
-
subject (nullable, optional):
- If provided, this subject will override the default subject set at the offer level.
{
"cc_emails": ["manager@example.com", "audit@example.com"],
"subject": "New subject",
}
Validation rules
Before an offer can be sent, the following conditions must be met:
- Offer existence: The provided offer_id must correspond to an existing offer. If the offer does not exist, the request will be rejected.
- Company authorization: The logged-in user making the API request must belong to the same company_id as the offer. If the user is from a different company, the request will be denied.
- Offer status restrictions: If the offer has already been accepted or rejected, it cannot be resent.
- Expiration date validation: If the offer has an expiration_date set and this date is in the past, the offer cannot be resent because the recipient will be unable to open it.