DoConure

Code examples: cURL, JavaScript, React, PHP, WordPress, Java, C#, Go, Ruby

The same call in every common language: make one document and save it as a PDF. Every example sends "isTest": true, so it is free; remove that line when you are ready for a real document. Python and Salesforce (Apex) have their own pages, and Make, Zapier and n8n are explained without code.

The address is https://api.doconure.com/api/v1/compose/documents/generate, the key goes in Authorization: Bearer ..., and the template id is the one on your template's page in the console. Every failure answers with a stable code and a message: branch on the code, never on the message (all the codes).

Keep the key on a server. Never put it in code that runs in a browser, a mobile app or a WordPress page's JavaScript: anybody who opens the page can read it. React, Vue and WordPress below call your own server, which holds the key.

cURL

curl -X POST https://api.doconure.com/api/v1/compose/documents/generate \
  -H "Authorization: Bearer $DOCONURE_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"data": {"customer": {"name": "Acme Srl"}},
       "settings": {"templateId": "YOUR_TEMPLATE_ID", "isTest": true}}' \
  --output document.pdf

On Windows cmd.exe does not understand single quotes: put the body in a file and use -d @body.json, or use PowerShell.

JavaScript and Node.js

import { writeFile } from "node:fs/promises";

const res = await fetch("https://api.doconure.com/api/v1/compose/documents/generate", {
  method: "POST",
  headers: {
    Authorization: `Bearer ${process.env.DOCONURE_API_KEY}`,
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
    data: { customer: { name: "Acme Srl" } },
    settings: { templateId: "YOUR_TEMPLATE_ID", isTest: true },
  }),
});
if (!res.ok) {
  const err = await res.json();
  throw new Error(`${err.code}: ${err.message}`);
}
await writeFile("document.pdf", Buffer.from(await res.arrayBuffer()));

React, Next.js and Vue

The browser never calls DoConure directly. It calls a route of your own server, and the route holds the key. Next.js route handler (app/api/invoice/route.js):

export async function POST(request) {
  const data = await request.json();
  const res = await fetch("https://api.doconure.com/api/v1/compose/documents/generate", {
    method: "POST",
    headers: {
      Authorization: `Bearer ${process.env.DOCONURE_API_KEY}`,
      "Content-Type": "application/json",
    },
    body: JSON.stringify({ data, settings: { templateId: "YOUR_TEMPLATE_ID" } }),
  });
  if (!res.ok) return Response.json(await res.json(), { status: res.status });
  return new Response(res.body, { headers: { "Content-Type": "application/pdf" } });
}

The React component that downloads the PDF:

export function DownloadInvoice({ invoice }) {
  async function download() {
    const res = await fetch("/api/invoice", {
      method: "POST",
      headers: { "Content-Type": "application/json" },
      body: JSON.stringify(invoice),
    });
    if (!res.ok) { alert((await res.json()).message); return; }
    const url = URL.createObjectURL(await res.blob());
    Object.assign(document.createElement("a"), { href: url, download: "invoice.pdf" }).click();
  }
  return <button onClick={download}>Download PDF</button>;
}

Vue, Svelte and Angular work the same way: the front end calls your server, your server calls DoConure.

PHP

<?php
$body = json_encode([
    "data" => ["customer" => ["name" => "Acme Srl"]],
    "settings" => ["templateId" => "YOUR_TEMPLATE_ID", "isTest" => true],
]);
$ch = curl_init("https://api.doconure.com/api/v1/compose/documents/generate");
curl_setopt_array($ch, [
    CURLOPT_POST => true,
    CURLOPT_POSTFIELDS => $body,
    CURLOPT_HTTPHEADER => [
        "Authorization: Bearer " . getenv("DOCONURE_API_KEY"),
        "Content-Type: application/json",
    ],
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_TIMEOUT => 120,
]);
$pdf = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
if ($status !== 200) {
    $err = json_decode($pdf, true);
    throw new RuntimeException($err["code"] . ": " . $err["message"]);
}
file_put_contents("document.pdf", $pdf);

Laravel and Symfony: the same call with Http::withToken(...)->post(...) or HttpClient; the body and the headers do not change.

WordPress

In a plugin or the theme's functions.php. Put the key in wp-config.php (define('DOCONURE_API_KEY', '...');), never in a post or in JavaScript:

function doconure_pdf( array $data, string $template_id ) {
    $response = wp_remote_post(
        'https://api.doconure.com/api/v1/compose/documents/generate',
        [
            'timeout' => 120,
            'headers' => [
                'Authorization' => 'Bearer ' . DOCONURE_API_KEY,
                'Content-Type'  => 'application/json',
            ],
            'body'    => wp_json_encode( [
                'data'     => $data,
                'settings' => [ 'templateId' => $template_id, 'isTest' => true ],
            ] ),
        ]
    );
    if ( is_wp_error( $response ) || 200 !== wp_remote_retrieve_response_code( $response ) ) {
        $err = json_decode( wp_remote_retrieve_body( $response ), true );
        return new WP_Error( $err['code'] ?? 'doconure_error', $err['message'] ?? 'Request failed' );
    }
    return wp_remote_retrieve_body( $response ); // the PDF bytes
}

WooCommerce: call it from the woocommerce_order_status_completed action and attach the file to the order e-mail. Contact Form 7 and Gravity Forms: call it from their submission hook.

Java

import java.net.URI;
import java.net.http.*;
import java.nio.file.*;

String body = """
    {"data": {"customer": {"name": "Acme Srl"}},
     "settings": {"templateId": "YOUR_TEMPLATE_ID", "isTest": true}}""";

HttpRequest request = HttpRequest.newBuilder()
    .uri(URI.create("https://api.doconure.com/api/v1/compose/documents/generate"))
    .header("Authorization", "Bearer " + System.getenv("DOCONURE_API_KEY"))
    .header("Content-Type", "application/json")
    .POST(HttpRequest.BodyPublishers.ofString(body))
    .build();

HttpResponse<byte[]> response = HttpClient.newHttpClient()
    .send(request, HttpResponse.BodyHandlers.ofByteArray());
if (response.statusCode() != 200) {
    throw new IllegalStateException(new String(response.body()));  // JSON with code and message
}
Files.write(Path.of("document.pdf"), response.body());

Spring: the same with RestClient or WebClient. Kotlin and Android use the same HttpClient, from a service of yours, not from the app.

C# and .NET

using System.Net.Http.Headers;
using System.Text;

using var http = new HttpClient { Timeout = TimeSpan.FromSeconds(120) };
http.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue(
    "Bearer", Environment.GetEnvironmentVariable("DOCONURE_API_KEY"));

var body = """
    {"data": {"customer": {"name": "Acme Srl"}},
     "settings": {"templateId": "YOUR_TEMPLATE_ID", "isTest": true}}""";

var response = await http.PostAsync(
    "https://api.doconure.com/api/v1/compose/documents/generate",
    new StringContent(body, Encoding.UTF8, "application/json"));
if (!response.IsSuccessStatusCode)
    throw new InvalidOperationException(await response.Content.ReadAsStringAsync());
await File.WriteAllBytesAsync("document.pdf", await response.Content.ReadAsByteArrayAsync());

To generate a typed client instead, download /openapi.json and feed it to NSwag, Kiota or openapi-generator (any language).

Go

body := `{"data": {"customer": {"name": "Acme Srl"}},
          "settings": {"templateId": "YOUR_TEMPLATE_ID", "isTest": true}}`
req, _ := http.NewRequest("POST",
    "https://api.doconure.com/api/v1/compose/documents/generate", strings.NewReader(body))
req.Header.Set("Authorization", "Bearer "+os.Getenv("DOCONURE_API_KEY"))
req.Header.Set("Content-Type", "application/json")

res, err := (&http.Client{Timeout: 120 * time.Second}).Do(req)
if err != nil { log.Fatal(err) }
defer res.Body.Close()
out, _ := io.ReadAll(res.Body)
if res.StatusCode != 200 { log.Fatalf("%d: %s", res.StatusCode, out) }
os.WriteFile("document.pdf", out, 0o644)

Ruby and Rails

require "net/http"
require "json"

uri = URI("https://api.doconure.com/api/v1/compose/documents/generate")
request = Net::HTTP::Post.new(uri, "Content-Type" => "application/json",
                              "Authorization" => "Bearer #{ENV['DOCONURE_API_KEY']}")
request.body = { data: { customer: { name: "Acme Srl" } },
                 settings: { templateId: "YOUR_TEMPLATE_ID", isTest: true } }.to_json
response = Net::HTTP.start(uri.hostname, uri.port, use_ssl: true, read_timeout: 120) { |h| h.request(request) }
raise response.body unless response.code == "200"   # JSON with code and message
File.binwrite("document.pdf", response.body)

Where to go next