diff --git a/sdk/go/README.md b/sdk/go/README.md
new file mode 100644
index 0000000..9102961
--- /dev/null
+++ b/sdk/go/README.md
@@ -0,0 +1,151 @@
+# DocFast Go SDK
+
+Official Go client for the [DocFast](https://docfast.dev) HTML/Markdown to PDF API.
+
+## Installation
+
+```bash
+go get github.com/docfast/docfast-go
+```
+
+## Quick Start
+
+```go
+package main
+
+import (
+ "os"
+ docfast "github.com/docfast/docfast-go"
+)
+
+func main() {
+ client := docfast.New("df_pro_your_api_key")
+
+ // HTML to PDF
+ pdf, err := client.HTML("
Hello World
Generated with DocFast
", nil)
+ if err != nil {
+ panic(err)
+ }
+ os.WriteFile("output.pdf", pdf, 0644)
+}
+```
+
+## Usage
+
+### HTML to PDF
+
+```go
+pdf, err := client.HTML("Hello
", &docfast.PDFOptions{
+ Format: "A4",
+ Landscape: true,
+ Margin: &docfast.Margin{Top: "20mm", Bottom: "20mm"},
+})
+```
+
+### HTML with custom CSS
+
+```go
+pdf, err := client.HTMLWithCSS(
+ "Styled
",
+ "h1 { color: navy; font-family: Georgia; }",
+ nil,
+)
+```
+
+### Markdown to PDF
+
+```go
+pdf, err := client.Markdown("# Report\n\nGenerated **automatically**.", nil)
+```
+
+### URL to PDF
+
+```go
+pdf, err := client.URL("https://example.com", &docfast.PDFOptions{
+ Format: "Letter",
+ PrintBackground: docfast.Bool(true),
+})
+```
+
+### Headers and Footers
+
+```go
+pdf, err := client.HTML(html, &docfast.PDFOptions{
+ DisplayHeaderFooter: true,
+ HeaderTemplate: `My Document
`,
+ FooterTemplate: `Page of
`,
+ Margin: &docfast.Margin{Top: "40mm", Bottom: "20mm"},
+})
+```
+
+### Custom Page Size
+
+```go
+pdf, err := client.HTML(html, &docfast.PDFOptions{
+ Width: "8.5in",
+ Height: "11in",
+ Scale: 0.8,
+})
+```
+
+### Templates
+
+```go
+// List available templates
+templates, err := client.Templates()
+
+// Render a template
+pdf, err := client.RenderTemplate("invoice", map[string]interface{}{
+ "company": "Acme Corp",
+ "items": []map[string]interface{}{{"name": "Widget", "price": 9.99}},
+ "_format": "A4",
+})
+```
+
+## PDF Options
+
+| Option | Type | Default | Description |
+|--------|------|---------|-------------|
+| `Format` | string | `"A4"` | Page size: A4, Letter, Legal, A3, A5, Tabloid |
+| `Landscape` | bool | `false` | Landscape orientation |
+| `Margin` | *Margin | `nil` | Page margins (CSS units) |
+| `PrintBackground` | *bool | `true` | Print background graphics |
+| `Filename` | string | `""` | Suggested filename |
+| `HeaderTemplate` | string | `""` | HTML header template |
+| `FooterTemplate` | string | `""` | HTML footer template |
+| `DisplayHeaderFooter` | bool | `false` | Show header/footer |
+| `Scale` | float64 | `1` | Rendering scale (0.1–2.0) |
+| `PageRanges` | string | `""` | Pages to print (e.g. "1-3,5") |
+| `PreferCSSPageSize` | bool | `false` | Prefer CSS @page size |
+| `Width` | string | `""` | Custom paper width |
+| `Height` | string | `""` | Custom paper height |
+
+## Error Handling
+
+```go
+pdf, err := client.HTML("Test
", nil)
+if err != nil {
+ var apiErr *docfast.Error
+ if errors.As(err, &apiErr) {
+ fmt.Printf("API error: %s (status %d)\n", apiErr.Message, apiErr.StatusCode)
+ }
+}
+```
+
+## Configuration
+
+```go
+// Custom base URL (e.g. for self-hosted or staging)
+client := docfast.New("key", docfast.WithBaseURL("https://staging.docfast.dev"))
+
+// Custom HTTP client
+client := docfast.New("key", docfast.WithHTTPClient(&http.Client{
+ Timeout: 120 * time.Second,
+}))
+```
+
+## Links
+
+- [Documentation](https://docfast.dev/docs)
+- [API Reference](https://docfast.dev/openapi.json)
+- [Get an API Key](https://docfast.dev)
diff --git a/sdk/go/docfast.go b/sdk/go/docfast.go
new file mode 100644
index 0000000..2491474
--- /dev/null
+++ b/sdk/go/docfast.go
@@ -0,0 +1,293 @@
+// Package docfast provides a Go client for the DocFast HTML/Markdown to PDF API.
+//
+// Usage:
+//
+// client := docfast.New("df_pro_your_key")
+// pdf, err := client.HTML("Hello
", nil)
+package docfast
+
+import (
+ "bytes"
+ "encoding/json"
+ "fmt"
+ "io"
+ "net/http"
+ "time"
+)
+
+const defaultBaseURL = "https://docfast.dev"
+
+// Margin defines PDF page margins using CSS units (e.g. "20mm", "1in").
+type Margin struct {
+ Top string `json:"top,omitempty"`
+ Bottom string `json:"bottom,omitempty"`
+ Left string `json:"left,omitempty"`
+ Right string `json:"right,omitempty"`
+}
+
+// PDFOptions configures PDF generation. All fields are optional.
+type PDFOptions struct {
+ // Page size: A4, Letter, Legal, A3, A5, Tabloid. Ignored if Width/Height set.
+ Format string `json:"format,omitempty"`
+ // Landscape orientation.
+ Landscape bool `json:"landscape,omitempty"`
+ // Page margins.
+ Margin *Margin `json:"margin,omitempty"`
+ // Print background graphics and colors. Default: true.
+ PrintBackground *bool `json:"printBackground,omitempty"`
+ // Suggested filename for the PDF download.
+ Filename string `json:"filename,omitempty"`
+ // HTML template for page header. Requires DisplayHeaderFooter: true.
+ // Supports CSS classes: date, title, url, pageNumber, totalPages.
+ HeaderTemplate string `json:"headerTemplate,omitempty"`
+ // HTML template for page footer. Same classes as HeaderTemplate.
+ FooterTemplate string `json:"footerTemplate,omitempty"`
+ // Show header and footer templates.
+ DisplayHeaderFooter bool `json:"displayHeaderFooter,omitempty"`
+ // Scale of webpage rendering (0.1 to 2.0). Default: 1.
+ Scale float64 `json:"scale,omitempty"`
+ // Paper ranges to print, e.g. "1-3,5".
+ PageRanges string `json:"pageRanges,omitempty"`
+ // Give CSS @page size priority over Format.
+ PreferCSSPageSize bool `json:"preferCSSPageSize,omitempty"`
+ // Paper width with units (e.g. "8.5in"). Overrides Format.
+ Width string `json:"width,omitempty"`
+ // Paper height with units (e.g. "11in"). Overrides Format.
+ Height string `json:"height,omitempty"`
+}
+
+// Template describes an available PDF template.
+type Template struct {
+ ID string `json:"id"`
+ Name string `json:"name"`
+ Description string `json:"description,omitempty"`
+}
+
+// Error is returned when the API responds with an error.
+type Error struct {
+ StatusCode int
+ Message string
+ Code string
+}
+
+func (e *Error) Error() string {
+ if e.Code != "" {
+ return fmt.Sprintf("docfast: %s (code=%s, status=%d)", e.Message, e.Code, e.StatusCode)
+ }
+ return fmt.Sprintf("docfast: %s (status=%d)", e.Message, e.StatusCode)
+}
+
+// ClientOption configures the Client.
+type ClientOption func(*Client)
+
+// WithBaseURL sets a custom API base URL.
+func WithBaseURL(url string) ClientOption {
+ return func(c *Client) { c.baseURL = url }
+}
+
+// WithHTTPClient sets a custom http.Client.
+func WithHTTPClient(hc *http.Client) ClientOption {
+ return func(c *Client) { c.httpClient = hc }
+}
+
+// Client is the DocFast API client.
+type Client struct {
+ apiKey string
+ baseURL string
+ httpClient *http.Client
+}
+
+// New creates a new DocFast client.
+func New(apiKey string, opts ...ClientOption) *Client {
+ c := &Client{
+ apiKey: apiKey,
+ baseURL: defaultBaseURL,
+ httpClient: &http.Client{
+ Timeout: 60 * time.Second,
+ },
+ }
+ for _, o := range opts {
+ o(c)
+ }
+ return c
+}
+
+func (c *Client) post(path string, body map[string]interface{}) ([]byte, error) {
+ data, err := json.Marshal(body)
+ if err != nil {
+ return nil, fmt.Errorf("docfast: marshal error: %w", err)
+ }
+
+ req, err := http.NewRequest("POST", c.baseURL+path, bytes.NewReader(data))
+ if err != nil {
+ return nil, fmt.Errorf("docfast: request error: %w", err)
+ }
+ req.Header.Set("Authorization", "Bearer "+c.apiKey)
+ req.Header.Set("Content-Type", "application/json")
+
+ resp, err := c.httpClient.Do(req)
+ if err != nil {
+ return nil, fmt.Errorf("docfast: request failed: %w", err)
+ }
+ defer resp.Body.Close()
+
+ respBody, err := io.ReadAll(resp.Body)
+ if err != nil {
+ return nil, fmt.Errorf("docfast: read error: %w", err)
+ }
+
+ if resp.StatusCode >= 400 {
+ apiErr := &Error{StatusCode: resp.StatusCode, Message: fmt.Sprintf("HTTP %d", resp.StatusCode)}
+ var errResp struct {
+ Error string `json:"error"`
+ Code string `json:"code"`
+ }
+ if json.Unmarshal(respBody, &errResp) == nil {
+ if errResp.Error != "" {
+ apiErr.Message = errResp.Error
+ }
+ apiErr.Code = errResp.Code
+ }
+ return nil, apiErr
+ }
+
+ return respBody, nil
+}
+
+func (c *Client) get(path string) ([]byte, error) {
+ req, err := http.NewRequest("GET", c.baseURL+path, nil)
+ if err != nil {
+ return nil, fmt.Errorf("docfast: request error: %w", err)
+ }
+ req.Header.Set("Authorization", "Bearer "+c.apiKey)
+
+ resp, err := c.httpClient.Do(req)
+ if err != nil {
+ return nil, fmt.Errorf("docfast: request failed: %w", err)
+ }
+ defer resp.Body.Close()
+
+ respBody, err := io.ReadAll(resp.Body)
+ if err != nil {
+ return nil, fmt.Errorf("docfast: read error: %w", err)
+ }
+
+ if resp.StatusCode >= 400 {
+ apiErr := &Error{StatusCode: resp.StatusCode, Message: fmt.Sprintf("HTTP %d", resp.StatusCode)}
+ var errResp struct {
+ Error string `json:"error"`
+ Code string `json:"code"`
+ }
+ if json.Unmarshal(respBody, &errResp) == nil && errResp.Error != "" {
+ apiErr.Message = errResp.Error
+ apiErr.Code = errResp.Code
+ }
+ return nil, apiErr
+ }
+
+ return respBody, nil
+}
+
+func mergeOptions(body map[string]interface{}, opts *PDFOptions) {
+ if opts == nil {
+ return
+ }
+ if opts.Format != "" {
+ body["format"] = opts.Format
+ }
+ if opts.Landscape {
+ body["landscape"] = true
+ }
+ if opts.Margin != nil {
+ body["margin"] = opts.Margin
+ }
+ if opts.PrintBackground != nil {
+ body["printBackground"] = *opts.PrintBackground
+ }
+ if opts.Filename != "" {
+ body["filename"] = opts.Filename
+ }
+ if opts.HeaderTemplate != "" {
+ body["headerTemplate"] = opts.HeaderTemplate
+ }
+ if opts.FooterTemplate != "" {
+ body["footerTemplate"] = opts.FooterTemplate
+ }
+ if opts.DisplayHeaderFooter {
+ body["displayHeaderFooter"] = true
+ }
+ if opts.Scale != 0 {
+ body["scale"] = opts.Scale
+ }
+ if opts.PageRanges != "" {
+ body["pageRanges"] = opts.PageRanges
+ }
+ if opts.PreferCSSPageSize {
+ body["preferCSSPageSize"] = true
+ }
+ if opts.Width != "" {
+ body["width"] = opts.Width
+ }
+ if opts.Height != "" {
+ body["height"] = opts.Height
+ }
+}
+
+// HTML converts HTML content to PDF. Returns the raw PDF bytes.
+func (c *Client) HTML(html string, opts *PDFOptions) ([]byte, error) {
+ body := map[string]interface{}{"html": html}
+ mergeOptions(body, opts)
+ return c.post("/v1/convert/html", body)
+}
+
+// HTMLWithCSS converts an HTML fragment with optional CSS to PDF.
+func (c *Client) HTMLWithCSS(html, css string, opts *PDFOptions) ([]byte, error) {
+ body := map[string]interface{}{"html": html, "css": css}
+ mergeOptions(body, opts)
+ return c.post("/v1/convert/html", body)
+}
+
+// Markdown converts Markdown content to PDF.
+func (c *Client) Markdown(markdown string, opts *PDFOptions) ([]byte, error) {
+ body := map[string]interface{}{"markdown": markdown}
+ mergeOptions(body, opts)
+ return c.post("/v1/convert/markdown", body)
+}
+
+// MarkdownWithCSS converts Markdown with optional CSS to PDF.
+func (c *Client) MarkdownWithCSS(markdown, css string, opts *PDFOptions) ([]byte, error) {
+ body := map[string]interface{}{"markdown": markdown, "css": css}
+ mergeOptions(body, opts)
+ return c.post("/v1/convert/markdown", body)
+}
+
+// URL converts a web page at the given URL to PDF.
+func (c *Client) URL(url string, opts *PDFOptions) ([]byte, error) {
+ body := map[string]interface{}{"url": url}
+ mergeOptions(body, opts)
+ return c.post("/v1/convert/url", body)
+}
+
+// Templates returns the list of available PDF templates.
+func (c *Client) Templates() ([]Template, error) {
+ data, err := c.get("/v1/templates")
+ if err != nil {
+ return nil, err
+ }
+ var result struct {
+ Templates []Template `json:"templates"`
+ }
+ if err := json.Unmarshal(data, &result); err != nil {
+ return nil, fmt.Errorf("docfast: decode error: %w", err)
+ }
+ return result.Templates, nil
+}
+
+// RenderTemplate renders a template with the given data and returns PDF bytes.
+func (c *Client) RenderTemplate(templateID string, data map[string]interface{}) ([]byte, error) {
+ body := map[string]interface{}{"data": data}
+ return c.post("/v1/templates/"+templateID, body)
+}
+
+// Bool is a helper to create a *bool for PDFOptions.PrintBackground.
+func Bool(v bool) *bool { return &v }
diff --git a/sdk/go/go.mod b/sdk/go/go.mod
new file mode 100644
index 0000000..3c69be7
--- /dev/null
+++ b/sdk/go/go.mod
@@ -0,0 +1,3 @@
+module github.com/docfast/docfast-go
+
+go 1.21
diff --git a/sdk/laravel/README.md b/sdk/laravel/README.md
new file mode 100644
index 0000000..117d57a
--- /dev/null
+++ b/sdk/laravel/README.md
@@ -0,0 +1,114 @@
+# DocFast for Laravel
+
+Official Laravel integration for the [DocFast](https://docfast.dev) HTML/Markdown to PDF API.
+
+## Installation
+
+```bash
+composer require docfast/laravel
+```
+
+Add your API key to `.env`:
+
+```env
+DOCFAST_API_KEY=df_pro_your_api_key
+```
+
+Publish the config (optional):
+
+```bash
+php artisan vendor:publish --tag=docfast-config
+```
+
+## Usage
+
+### Via Facade
+
+```php
+use DocFast\Laravel\Facades\DocFast;
+
+// HTML to PDF
+$pdf = DocFast::html('Invoice
Total: €99.00
');
+return response($pdf)
+ ->header('Content-Type', 'application/pdf')
+ ->header('Content-Disposition', 'inline; filename="invoice.pdf"');
+```
+
+### Via Dependency Injection
+
+```php
+use DocFast\Client;
+
+class InvoiceController extends Controller
+{
+ public function download(Client $docfast)
+ {
+ $pdf = $docfast->html(view('invoice')->render());
+ return response($pdf)
+ ->header('Content-Type', 'application/pdf');
+ }
+}
+```
+
+### Markdown to PDF
+
+```php
+$pdf = DocFast::markdown('# Report\n\nGenerated at ' . now());
+```
+
+### URL to PDF
+
+```php
+$pdf = DocFast::url('https://example.com');
+```
+
+### With PDF Options
+
+```php
+use DocFast\PdfOptions;
+
+$options = new PdfOptions();
+$options->format = 'Letter';
+$options->landscape = true;
+$options->margin = ['top' => '20mm', 'bottom' => '20mm'];
+
+$pdf = DocFast::html($html, null, $options);
+```
+
+### Headers and Footers
+
+```php
+$options = new PdfOptions();
+$options->displayHeaderFooter = true;
+$options->footerTemplate = 'Page
';
+$options->margin = ['top' => '10mm', 'bottom' => '20mm'];
+
+$pdf = DocFast::html(view('report')->render(), null, $options);
+```
+
+### Templates
+
+```php
+$pdf = DocFast::renderTemplate('invoice', [
+ 'company' => 'Acme Corp',
+ 'items' => [['name' => 'Widget', 'price' => 9.99]],
+]);
+```
+
+## Configuration
+
+```php
+// config/docfast.php
+return [
+ 'api_key' => env('DOCFAST_API_KEY'),
+ 'base_url' => env('DOCFAST_BASE_URL', 'https://docfast.dev'),
+ 'timeout' => env('DOCFAST_TIMEOUT', 60),
+];
+```
+
+## Links
+
+- [PHP SDK](../php/) — standalone PHP client
+- [Documentation](https://docfast.dev/docs)
+- [API Reference](https://docfast.dev/openapi.json)
+- [Get an API Key](https://docfast.dev)
diff --git a/sdk/laravel/composer.json b/sdk/laravel/composer.json
new file mode 100644
index 0000000..312f21d
--- /dev/null
+++ b/sdk/laravel/composer.json
@@ -0,0 +1,34 @@
+{
+ "name": "docfast/laravel",
+ "description": "Laravel integration for the DocFast HTML/Markdown to PDF API",
+ "type": "library",
+ "license": "MIT",
+ "homepage": "https://docfast.dev",
+ "keywords": ["pdf", "html-to-pdf", "laravel", "docfast"],
+ "require": {
+ "php": "^8.1",
+ "illuminate/support": "^10.0|^11.0|^12.0",
+ "docfast/docfast-php": "^1.0"
+ },
+ "autoload": {
+ "psr-4": {
+ "DocFast\\Laravel\\": "src/"
+ }
+ },
+ "extra": {
+ "laravel": {
+ "providers": [
+ "DocFast\\Laravel\\DocFastServiceProvider"
+ ],
+ "aliases": {
+ "DocFast": "DocFast\\Laravel\\Facades\\DocFast"
+ }
+ }
+ },
+ "authors": [
+ {
+ "name": "DocFast",
+ "homepage": "https://docfast.dev"
+ }
+ ]
+}
diff --git a/sdk/laravel/config/docfast.php b/sdk/laravel/config/docfast.php
new file mode 100644
index 0000000..440ed83
--- /dev/null
+++ b/sdk/laravel/config/docfast.php
@@ -0,0 +1,33 @@
+ env('DOCFAST_API_KEY'),
+
+ /*
+ |--------------------------------------------------------------------------
+ | Base URL
+ |--------------------------------------------------------------------------
+ |
+ | The DocFast API base URL. Change for staging or self-hosted instances.
+ |
+ */
+ 'base_url' => env('DOCFAST_BASE_URL', 'https://docfast.dev'),
+
+ /*
+ |--------------------------------------------------------------------------
+ | Timeout
+ |--------------------------------------------------------------------------
+ |
+ | Request timeout in seconds for PDF generation.
+ |
+ */
+ 'timeout' => env('DOCFAST_TIMEOUT', 60),
+];
diff --git a/sdk/laravel/src/DocFastServiceProvider.php b/sdk/laravel/src/DocFastServiceProvider.php
new file mode 100644
index 0000000..6c0d3d5
--- /dev/null
+++ b/sdk/laravel/src/DocFastServiceProvider.php
@@ -0,0 +1,34 @@
+mergeConfigFrom(__DIR__ . '/../config/docfast.php', 'docfast');
+
+ $this->app->singleton(Client::class, function ($app) {
+ $config = $app['config']['docfast'];
+ return new Client(
+ $config['api_key'] ?? '',
+ $config['base_url'] ?? 'https://docfast.dev',
+ $config['timeout'] ?? 60,
+ );
+ });
+
+ $this->app->alias(Client::class, 'docfast');
+ }
+
+ public function boot(): void
+ {
+ $this->publishes([
+ __DIR__ . '/../config/docfast.php' => config_path('docfast.php'),
+ ], 'docfast-config');
+ }
+}
diff --git a/sdk/laravel/src/Facades/DocFast.php b/sdk/laravel/src/Facades/DocFast.php
new file mode 100644
index 0000000..99f895d
--- /dev/null
+++ b/sdk/laravel/src/Facades/DocFast.php
@@ -0,0 +1,25 @@
+html('Hello World
');
+file_put_contents('output.pdf', $pdf);
+```
+
+## Usage
+
+### HTML to PDF
+
+```php
+$pdf = $client->html('Hello
My document
');
+```
+
+### HTML with CSS
+
+```php
+$pdf = $client->html(
+ 'Styled
',
+ 'h1 { color: navy; font-family: Georgia; }'
+);
+```
+
+### HTML with PDF Options
+
+```php
+use DocFast\PdfOptions;
+
+$options = new PdfOptions();
+$options->format = 'Letter';
+$options->landscape = true;
+$options->margin = ['top' => '20mm', 'bottom' => '20mm', 'left' => '15mm', 'right' => '15mm'];
+$options->printBackground = true;
+
+$pdf = $client->html('Report
', null, $options);
+```
+
+### Markdown to PDF
+
+```php
+$pdf = $client->markdown('# Hello World\n\nThis is **bold** text.');
+```
+
+### URL to PDF
+
+```php
+$pdf = $client->url('https://example.com');
+```
+
+### Headers and Footers
+
+```php
+$options = new PdfOptions();
+$options->displayHeaderFooter = true;
+$options->headerTemplate = 'My Document
';
+$options->footerTemplate = 'Page /
';
+$options->margin = ['top' => '40mm', 'bottom' => '20mm'];
+
+$pdf = $client->html($html, null, $options);
+```
+
+### Custom Page Size
+
+```php
+$options = new PdfOptions();
+$options->width = '8.5in';
+$options->height = '11in';
+$options->scale = 0.8;
+
+$pdf = $client->html($html, null, $options);
+```
+
+### Templates
+
+```php
+// List templates
+$templates = $client->templates();
+
+// Render a template
+$pdf = $client->renderTemplate('invoice', [
+ 'company' => 'Acme Corp',
+ 'items' => [['name' => 'Widget', 'price' => 9.99]],
+]);
+```
+
+## PDF Options
+
+| Option | Type | Default | Description |
+|--------|------|---------|-------------|
+| `format` | string | `"A4"` | Page size: A4, Letter, Legal, A3, A5, Tabloid |
+| `landscape` | bool | `false` | Landscape orientation |
+| `margin` | array | `null` | Margins with top/bottom/left/right keys (CSS units) |
+| `printBackground` | bool | `true` | Print background graphics |
+| `filename` | string | `null` | Suggested filename |
+| `headerTemplate` | string | `null` | HTML header template |
+| `footerTemplate` | string | `null` | HTML footer template |
+| `displayHeaderFooter` | bool | `false` | Show header/footer |
+| `scale` | float | `1` | Rendering scale (0.1–2.0) |
+| `pageRanges` | string | `null` | Pages to print (e.g. "1-3,5") |
+| `preferCSSPageSize` | bool | `false` | Prefer CSS @page size |
+| `width` | string | `null` | Custom paper width |
+| `height` | string | `null` | Custom paper height |
+
+## Error Handling
+
+```php
+use DocFast\DocFastException;
+
+try {
+ $pdf = $client->html('Test
');
+} catch (DocFastException $e) {
+ echo "Error: {$e->getMessage()} (status: {$e->statusCode})\n";
+}
+```
+
+## Configuration
+
+```php
+// Custom base URL
+$client = new Client('key', 'https://staging.docfast.dev');
+
+// Custom timeout (seconds)
+$client = new Client('key', 'https://docfast.dev', 120);
+```
+
+## Laravel Integration
+
+See the [DocFast Laravel package](../laravel/) for a dedicated Laravel integration with facades, config, and service provider.
+
+## Links
+
+- [Documentation](https://docfast.dev/docs)
+- [API Reference](https://docfast.dev/openapi.json)
+- [Get an API Key](https://docfast.dev)
diff --git a/sdk/php/composer.json b/sdk/php/composer.json
new file mode 100644
index 0000000..5277c34
--- /dev/null
+++ b/sdk/php/composer.json
@@ -0,0 +1,24 @@
+{
+ "name": "docfast/docfast-php",
+ "description": "Official PHP SDK for the DocFast HTML/Markdown to PDF API",
+ "type": "library",
+ "license": "MIT",
+ "homepage": "https://docfast.dev",
+ "keywords": ["pdf", "html-to-pdf", "markdown-to-pdf", "api", "docfast"],
+ "require": {
+ "php": "^8.1",
+ "ext-json": "*",
+ "ext-curl": "*"
+ },
+ "autoload": {
+ "psr-4": {
+ "DocFast\\": "src/"
+ }
+ },
+ "authors": [
+ {
+ "name": "DocFast",
+ "homepage": "https://docfast.dev"
+ }
+ ]
+}
diff --git a/sdk/php/src/Client.php b/sdk/php/src/Client.php
new file mode 100644
index 0000000..bee8426
--- /dev/null
+++ b/sdk/php/src/Client.php
@@ -0,0 +1,183 @@
+apiKey = $apiKey;
+ $this->baseUrl = rtrim($baseUrl, '/');
+ $this->timeout = $timeout;
+ }
+
+ /**
+ * Convert HTML to PDF.
+ *
+ * @param string $html HTML content
+ * @param string|null $css Optional CSS to inject
+ * @param PdfOptions|null $options PDF options
+ * @return string Raw PDF bytes
+ * @throws DocFastException
+ */
+ public function html(string $html, ?string $css = null, ?PdfOptions $options = null): string
+ {
+ $body = ['html' => $html];
+ if ($css !== null) {
+ $body['css'] = $css;
+ }
+ return $this->convert('/v1/convert/html', $body, $options);
+ }
+
+ /**
+ * Convert Markdown to PDF.
+ *
+ * @param string $markdown Markdown content
+ * @param string|null $css Optional CSS to inject
+ * @param PdfOptions|null $options PDF options
+ * @return string Raw PDF bytes
+ * @throws DocFastException
+ */
+ public function markdown(string $markdown, ?string $css = null, ?PdfOptions $options = null): string
+ {
+ $body = ['markdown' => $markdown];
+ if ($css !== null) {
+ $body['css'] = $css;
+ }
+ return $this->convert('/v1/convert/markdown', $body, $options);
+ }
+
+ /**
+ * Convert a URL to PDF.
+ *
+ * @param string $url URL to convert
+ * @param PdfOptions|null $options PDF options
+ * @return string Raw PDF bytes
+ * @throws DocFastException
+ */
+ public function url(string $url, ?PdfOptions $options = null): string
+ {
+ return $this->convert('/v1/convert/url', ['url' => $url], $options);
+ }
+
+ /**
+ * List available templates.
+ *
+ * @return array
+ * @throws DocFastException
+ */
+ public function templates(): array
+ {
+ $data = $this->get('/v1/templates');
+ $result = json_decode($data, true);
+ return $result['templates'] ?? [];
+ }
+
+ /**
+ * Render a template to PDF.
+ *
+ * @param string $templateId Template ID
+ * @param array $data Template data
+ * @return string Raw PDF bytes
+ * @throws DocFastException
+ */
+ public function renderTemplate(string $templateId, array $data = []): string
+ {
+ return $this->post('/v1/templates/' . urlencode($templateId), ['data' => $data]);
+ }
+
+ private function convert(string $path, array $body, ?PdfOptions $options): string
+ {
+ if ($options !== null) {
+ $body = array_merge($body, $options->toArray());
+ }
+ return $this->post($path, $body);
+ }
+
+ private function post(string $path, array $body): string
+ {
+ $ch = curl_init($this->baseUrl . $path);
+ curl_setopt_array($ch, [
+ CURLOPT_POST => true,
+ CURLOPT_POSTFIELDS => json_encode($body),
+ CURLOPT_RETURNTRANSFER => true,
+ CURLOPT_TIMEOUT => $this->timeout,
+ CURLOPT_HTTPHEADER => [
+ 'Authorization: Bearer ' . $this->apiKey,
+ 'Content-Type: application/json',
+ 'Accept: application/pdf',
+ ],
+ ]);
+
+ $response = curl_exec($ch);
+ $statusCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
+ $error = curl_error($ch);
+ curl_close($ch);
+
+ if ($response === false) {
+ throw new DocFastException('Request failed: ' . $error, 0);
+ }
+
+ if ($statusCode >= 400) {
+ $this->handleError($response, $statusCode);
+ }
+
+ return $response;
+ }
+
+ private function get(string $path): string
+ {
+ $ch = curl_init($this->baseUrl . $path);
+ curl_setopt_array($ch, [
+ CURLOPT_RETURNTRANSFER => true,
+ CURLOPT_TIMEOUT => $this->timeout,
+ CURLOPT_HTTPHEADER => [
+ 'Authorization: Bearer ' . $this->apiKey,
+ 'Accept: application/json',
+ ],
+ ]);
+
+ $response = curl_exec($ch);
+ $statusCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
+ $error = curl_error($ch);
+ curl_close($ch);
+
+ if ($response === false) {
+ throw new DocFastException('Request failed: ' . $error, 0);
+ }
+
+ if ($statusCode >= 400) {
+ $this->handleError($response, $statusCode);
+ }
+
+ return $response;
+ }
+
+ private function handleError(string $response, int $statusCode): never
+ {
+ $message = "HTTP $statusCode";
+ $code = null;
+
+ $data = json_decode($response, true);
+ if (is_array($data)) {
+ $message = $data['error'] ?? $message;
+ $code = $data['code'] ?? null;
+ }
+
+ throw new DocFastException($message, $statusCode, $code);
+ }
+}
diff --git a/sdk/php/src/DocFastException.php b/sdk/php/src/DocFastException.php
new file mode 100644
index 0000000..2b583f6
--- /dev/null
+++ b/sdk/php/src/DocFastException.php
@@ -0,0 +1,18 @@
+statusCode = $statusCode;
+ $this->errorCode = $errorCode;
+ parent::__construct($message, $statusCode, $previous);
+ }
+}
diff --git a/sdk/php/src/PdfOptions.php b/sdk/php/src/PdfOptions.php
new file mode 100644
index 0000000..c22384c
--- /dev/null
+++ b/sdk/php/src/PdfOptions.php
@@ -0,0 +1,65 @@
+$key !== null) {
+ $data[$key] = $this->$key;
+ }
+ }
+ return $data;
+ }
+}