# Enchaîner les actions

Comment passer la sortie d’une action directement à la suivante, avec un pipeline de trois étapes détaillé et les cas où l’enchaînement n’est pas le bon outil.

Chaque action prend un PDF en entrée et renvoie un PDF en sortie, de sorte que
la sortie d’une action est une entrée valide pour la suivante. Cela vous permet
de construire un pipeline en plusieurs étapes à partir d’actions à usage
unique : fusionner quelques fichiers, apposer un filigrane sur le résultat, puis
le chiffrer, le tout dans un même script, en passant les octets d’un appel à
l’autre en mémoire.

Comme l’API est *stateless* (rien n’est stocké entre les requêtes), il n’y a
aucun identifiant à réutiliser ni aucun nettoyage à faire. Chaque étape se
contente de transmettre le corps de sa réponse à l’étape suivante dans le champ
`file`.

## Comment cela fonctionne

Une action à sortie unique répond avec `200 OK` et `Content-Type:
application/pdf`. Le corps est un PDF complet. Pour enchaîner, prenez ce corps
et envoyez-le comme partie `file` de la requête suivante au lieu de lire un
fichier sur le disque. Seules la toute première entrée et la toute dernière
sortie ont besoin de toucher le système de fichiers ; tout ce qui se trouve entre
les deux reste en mémoire.

## Un pipeline de trois étapes

Cet exemple transforme deux fichiers source en un seul document chiffré et
filigrané.

<Steps>

<Step title="Fusionnez les fichiers source">
  `POST /v1/merge_documents` avec `cover.pdf` et `report.pdf` renvoie un seul
  PDF combiné. Consultez [Fusionner des documents
  PDF](/docs/api/merge-pdf-documents) pour les options d’entrée.
</Step>

<Step title="Apposez le filigrane sur les octets fusionnés">
  Envoyez ce PDF dans le champ `file` à `POST /v1/add_text_watermark`. La
  réponse est le même document avec un filigrane sur chaque page.
</Step>

<Step title="Chiffrez les octets filigranés">
  Envoyez le PDF filigrané à `POST /v1/add_password` avec un `password`. La
  réponse est le document final, protégé : écrivez-le sur le disque.
</Step>

</Steps>

<Info>
  La fusion accepte les PDF d’entrée sous forme de parties `file` répétées ou,
  lorsque répéter un nom de champ est peu commode (PHP, Ruby), sous forme de
  champs numérotés de `file_1` à `file_10`. Les deux formes sont montrées
  ci-dessous ; utilisez `file` répété lorsque vous en avez plus de dix.
</Info>

<CodeGroup>

```bash title="cURL"
# Each stage reads the previous PDF from stdin via `-F 'file=@-'`.
curl -sS https://api.pdfblocks.com/v1/merge_documents \
  -H 'X-API-Key: your_api_key' \
  -F file=@cover.pdf \
  -F file=@report.pdf |
curl -sS https://api.pdfblocks.com/v1/add_text_watermark \
  -H 'X-API-Key: your_api_key' \
  -F 'file=@-;filename=merged.pdf;type=application/pdf' \
  -F line_1='CONFIDENTIAL' \
  -F line_2='ACME, Inc.' |
curl -sS https://api.pdfblocks.com/v1/add_password \
  -H 'X-API-Key: your_api_key' \
  -F 'file=@-;filename=watermarked.pdf;type=application/pdf' \
  -F password='pa$$word' \
  -o final.pdf
```

```python title="Python"
# pip install requests
import requests

BASE = 'https://api.pdfblocks.com'
HEADERS = {'X-API-Key': 'your_api_key'}

# 1. Merge cover.pdf and report.pdf into one document.
with open('cover.pdf', 'rb') as cover, open('report.pdf', 'rb') as report:
    merged = requests.post(
        f'{BASE}/v1/merge_documents',
        headers=HEADERS,
        files=[('file', cover), ('file', report)],
    )
merged.raise_for_status()

# 2. Watermark the merged bytes: no temp file.
watermarked = requests.post(
    f'{BASE}/v1/add_text_watermark',
    headers=HEADERS,
    files={'file': ('merged.pdf', merged.content, 'application/pdf')},
    data={'line_1': 'CONFIDENTIAL', 'line_2': 'ACME, Inc.'},
)
watermarked.raise_for_status()

# 3. Encrypt the watermarked bytes.
final = requests.post(
    f'{BASE}/v1/add_password',
    headers=HEADERS,
    files={'file': ('watermarked.pdf', watermarked.content, 'application/pdf')},
    data={'password': 'pa$$word'},
)
final.raise_for_status()

with open('final.pdf', 'wb') as output:
    output.write(final.content)
```

```javascript title="Node.js"
// Node.js 18+
import { readFile, writeFile } from 'node:fs/promises';

const BASE = 'https://api.pdfblocks.com';
const headers = { 'X-API-Key': 'your_api_key' };

// Post a multipart form and return the response PDF as bytes.
async function post(path, form) {
  const response = await fetch(BASE + path, { method: 'POST', headers, body: form });
  if (!response.ok) throw new Error(`${path} failed: ${response.status}`);
  return new Uint8Array(await response.arrayBuffer());
}

// 1. Merge cover.pdf and report.pdf.
const mergeForm = new FormData();
mergeForm.append('file', new Blob([await readFile('cover.pdf')]), 'cover.pdf');
mergeForm.append('file', new Blob([await readFile('report.pdf')]), 'report.pdf');
const merged = await post('/v1/merge_documents', mergeForm);

// 2. Watermark the merged bytes.
const watermarkForm = new FormData();
watermarkForm.append('file', new Blob([merged]), 'merged.pdf');
watermarkForm.append('line_1', 'CONFIDENTIAL');
watermarkForm.append('line_2', 'ACME, Inc.');
const watermarked = await post('/v1/add_text_watermark', watermarkForm);

// 3. Encrypt the watermarked bytes.
const passwordForm = new FormData();
passwordForm.append('file', new Blob([watermarked]), 'watermarked.pdf');
passwordForm.append('password', 'pa$$word');
const final = await post('/v1/add_password', passwordForm);

await writeFile('final.pdf', final);
```

```php title="PHP"
<?php
$base = 'https://api.pdfblocks.com';
$headers = ['X-API-Key: your_api_key'];

// Post a multipart form and return the response body, or throw on error.
function post(string $url, array $headers, array $fields): string {
    $ch = curl_init($url);
    curl_setopt_array($ch, [
        CURLOPT_RETURNTRANSFER => true,
        CURLOPT_HTTPHEADER => $headers,
        CURLOPT_POST => true,
        CURLOPT_POSTFIELDS => $fields,
    ]);
    $body = curl_exec($ch);
    $status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
    if ($status !== 200) {
        throw new RuntimeException("Request to $url failed with status $status.");
    }
    return $body;
}

// 1. Merge cover.pdf and report.pdf (numbered fields for the array input).
$merged = post("$base/v1/merge_documents", $headers, [
    'file_1' => new CURLFile('cover.pdf', 'application/pdf'),
    'file_2' => new CURLFile('report.pdf', 'application/pdf'),
]);

// 2. Watermark the merged bytes in memory (CURLStringFile, PHP 8.1+).
$watermarked = post("$base/v1/add_text_watermark", $headers, [
    'file' => new CURLStringFile($merged, 'merged.pdf', 'application/pdf'),
    'line_1' => 'CONFIDENTIAL',
    'line_2' => 'ACME, Inc.',
]);

// 3. Encrypt the watermarked bytes.
$final = post("$base/v1/add_password", $headers, [
    'file' => new CURLStringFile($watermarked, 'watermarked.pdf', 'application/pdf'),
    'password' => 'pa$$word',
]);

file_put_contents('final.pdf', $final);
```

```ruby title="Ruby"
# gem install http
require 'http'
require 'stringio'

BASE = 'https://api.pdfblocks.com'
HEADERS = { 'X-API-Key' => 'your_api_key' }

def post(path, fields)
  response = HTTP.headers(HEADERS).post("#{BASE}#{path}", form: fields)
  raise "#{path} failed: #{response.status}" unless response.status.success?
  response.body.to_s
end

# 1. Merge cover.pdf and report.pdf (numbered fields for the array input).
merged = post('/v1/merge_documents',
  file_1: HTTP::FormData::File.new('cover.pdf'),
  file_2: HTTP::FormData::File.new('report.pdf'))

# 2. Watermark the merged bytes in memory.
watermarked = post('/v1/add_text_watermark',
  file: HTTP::FormData::File.new(StringIO.new(merged),
    filename: 'merged.pdf', content_type: 'application/pdf'),
  line_1: 'CONFIDENTIAL',
  line_2: 'ACME, Inc.')

# 3. Encrypt the watermarked bytes.
final = post('/v1/add_password',
  file: HTTP::FormData::File.new(StringIO.new(watermarked),
    filename: 'watermarked.pdf', content_type: 'application/pdf'),
  password: 'pa$$word')

File.write('final.pdf', final)
```

```go title="Go"
package main

import (
	"bytes"
	"fmt"
	"io"
	"mime/multipart"
	"net/http"
	"os"
)

const base = "https://api.pdfblocks.com"

// post sends a multipart form and returns the response PDF bytes.
func post(path string, build func(*multipart.Writer)) []byte {
	var buf bytes.Buffer
	form := multipart.NewWriter(&buf)
	build(form)
	form.Close()

	req, _ := http.NewRequest("POST", base+path, &buf)
	req.Header.Set("Content-Type", form.FormDataContentType())
	req.Header.Set("X-API-Key", "your_api_key")

	res, err := http.DefaultClient.Do(req)
	if err != nil {
		panic(err)
	}
	defer res.Body.Close()

	body, _ := io.ReadAll(res.Body)
	if res.StatusCode != http.StatusOK {
		panic(fmt.Sprintf("%s failed: %d", path, res.StatusCode))
	}
	return body
}

func addFile(form *multipart.Writer, name string, data []byte) {
	part, _ := form.CreateFormFile("file", name)
	part.Write(data)
}

func main() {
	cover, _ := os.ReadFile("cover.pdf")
	report, _ := os.ReadFile("report.pdf")

	// 1. Merge cover.pdf and report.pdf.
	merged := post("/v1/merge_documents", func(form *multipart.Writer) {
		addFile(form, "cover.pdf", cover)
		addFile(form, "report.pdf", report)
	})

	// 2. Watermark the merged bytes.
	watermarked := post("/v1/add_text_watermark", func(form *multipart.Writer) {
		addFile(form, "merged.pdf", merged)
		form.WriteField("line_1", "CONFIDENTIAL")
		form.WriteField("line_2", "ACME, Inc.")
	})

	// 3. Encrypt the watermarked bytes.
	final := post("/v1/add_password", func(form *multipart.Writer) {
		addFile(form, "watermarked.pdf", watermarked)
		form.WriteField("password", "pa$$word")
	})

	os.WriteFile("final.pdf", final, 0644)
}
```

```csharp title="C#"
using System.Net.Http.Headers;

const string Base = "https://api.pdfblocks.com";

using var client = new HttpClient();
client.DefaultRequestHeaders.Add("X-API-Key", "your_api_key");

// Post a multipart form and return the response PDF bytes.
async Task<byte[]> Post(string path, Action<MultipartFormDataContent> build)
{
    using var form = new MultipartFormDataContent();
    build(form);
    var response = await client.PostAsync(Base + path, form);
    response.EnsureSuccessStatusCode();
    return await response.Content.ReadAsByteArrayAsync();
}

static void AddFile(MultipartFormDataContent form, string name, byte[] data)
{
    var content = new ByteArrayContent(data);
    content.Headers.ContentType = new MediaTypeHeaderValue("application/pdf");
    form.Add(content, "file", name);
}

// 1. Merge cover.pdf and report.pdf.
var merged = await Post("/v1/merge_documents", form =>
{
    AddFile(form, "cover.pdf", File.ReadAllBytes("cover.pdf"));
    AddFile(form, "report.pdf", File.ReadAllBytes("report.pdf"));
});

// 2. Watermark the merged bytes.
var watermarked = await Post("/v1/add_text_watermark", form =>
{
    AddFile(form, "merged.pdf", merged);
    form.Add(new StringContent("CONFIDENTIAL"), "line_1");
    form.Add(new StringContent("ACME, Inc."), "line_2");
});

// 3. Encrypt the watermarked bytes.
var final = await Post("/v1/add_password", form =>
{
    AddFile(form, "watermarked.pdf", watermarked);
    form.Add(new StringContent("pa$$word"), "password");
});

await File.WriteAllBytesAsync("final.pdf", final);
```

</CodeGroup>

## Vérifiez chaque étape avant de transmettre sa sortie

Une étape en échec renvoie un `4XX` avec un corps `application/problem+json`,
pas un PDF. Si vous injectez ce corps d’erreur dans la requête suivante comme
`file`, l’étape suivante échouera elle aussi, avec un message déroutant.
Vérifiez le statut de chaque réponse avant d’en transmettre le corps : les
exemples ci-dessus lèvent une erreur sur toute réponse autre que `200`.
Consultez [Erreurs](/docs/api/errors) pour la forme de la réponse.

<Warning>
  Apposez le filigrane et fusionnez avant de chiffrer. Une fois qu’[Ajouter un
  mot de passe à un PDF](/docs/api/add-password-to-pdf) a chiffré un document,
  les actions suivantes ne peuvent plus l’ouvrir : toute étape de mot de passe a
  donc sa place à la **fin** du pipeline. Consultez [Protéger et déverrouiller
  des documents](/docs/api/protecting-documents) pour toutes les règles d’ordre.
</Warning>

## Quand enchaîner, et quand ne pas le faire

Enchaînez lorsque chaque étape est une transformation distincte que vous voulez
appliquer en séquence. Faites de chaque étape un appel pur, à usage unique :
c’est ce qui fait de la sortie de l’une une entrée valide pour la suivante, et
ce qui rend les échecs faciles à localiser.

Certaines tâches ressemblent à un enchaînement sans en être un. Réorganiser et
écarter des pages en une seule passe, c’est un unique appel à [Réorganiser
les pages d’un PDF](/docs/api/reorder-pages-of-pdf), pas une extraction suivie
d’une fusion. N’ayez recours à un pipeline que lorsque aucune action seule ne
fait tout le travail.

## Voir aussi

<CardGroup cols={2}>

<Card title="Fusionner des documents PDF" href="/docs/api/merge-pdf-documents">
  Combinez des fichiers comme première étape d’un pipeline.
</Card>

<Card title="Ajouter un filigrane texte" href="/docs/api/add-text-watermark-to-pdf">
  Apposez du texte sur les pages de n’importe quel PDF.
</Card>

<Card title="Protéger et déverrouiller des documents" href="/docs/api/protecting-documents">
  Ordonnez correctement les actions de mot de passe et de restriction.
</Card>

<Card title="Travailler avec de gros fichiers" href="/docs/api/working-with-large-files">
  Faites circuler en flux les entrées et sorties d’un pipeline sans mise en
  mémoire tampon.
</Card>

</CardGroup>
