# Calling the API

> A read and a write in JavaScript (fetch), Python (requests) and PHP (Guzzle), with the headers, error handling and exact amounts every integration needs.

Web page: https://developer.yoprint.com/concepts/calling-the-api/

The rest of these docs show requests with `curl`. This page shows the same pattern in three languages: one helper that every call goes through, a read, and a write.

The helper does five things on every request:

1. Sends the key as `Authorization: Bearer YOUR_API_KEY`.
2. Sends JSON with `Content-Type: application/json`.
3. Asks for JSON with `Accept: application/json`, so even a wrong path answers JSON.
4. Asks for exact amounts with `X-Yoprint-Decimal-Format: exact-v1`, so values that cannot be represented as exact JSON numbers (such as fractional thousandths or large integers) return as strings (see [Money and quantities](https://developer.yoprint.com/concepts/money-and-quantities.md)).
5. Treats an answer as failed when its status is `4xx` or `5xx` **or** its body has `"success": false`, and raises an error that carries the status and the body (see [Errors](https://developer.yoprint.com/concepts/errors.md)).

The helper only parses JSON. For a file download, such as a CSV export or a PDF, read the response body as bytes instead.

Keep the key in an environment variable, here `YOPRINT_API_KEY`, never in the code.

## The helper

**JavaScript**

Node.js 18 or later, or any runtime with `fetch`.

```javascript
const BASE = 'https://secure.yoprint.com';
const TEAM = 'your-team';

export async function yoprint(method, path, body) {
  const response = await fetch(`${BASE}${path.replace('{team_slug}', TEAM)}`, {
    method,
    headers: {
      Authorization: `Bearer ${process.env.YOPRINT_API_KEY}`,
      'Content-Type': 'application/json',
      Accept: 'application/json',
      'X-Yoprint-Decimal-Format': 'exact-v1',
    },
    body: body === undefined ? undefined : JSON.stringify(body),
  });
  if (response.status === 204) return null; // one delete answers with no body
  const json = await response.json().catch(() => null);
  if (!response.ok || json === null || json.success === false) {
    const error = new Error(`YoPrint ${response.status}: ${json?.error?.code ?? json?.message ?? 'error'}`);
    error.status = response.status;
    error.body = json;
    throw error;
  }
  return json;
}
```

**Python**

Python 3 with `requests`.

```python
import os
import requests

BASE = "https://secure.yoprint.com"
TEAM = "your-team"

session = requests.Session()
session.headers.update({
    "Authorization": f"Bearer {os.environ['YOPRINT_API_KEY']}",
    "Content-Type": "application/json",
    "Accept": "application/json",
    "X-Yoprint-Decimal-Format": "exact-v1",
})


class YoPrintError(Exception):
    def __init__(self, status, body):
        super().__init__(f"YoPrint {status}")
        self.status = status
        self.body = body


def yoprint(method, path, body=None):
    response = session.request(
        method, BASE + path.replace("{team_slug}", TEAM), json=body, timeout=30
    )
    if response.status_code == 204:  # one delete answers with no body
        return None
    try:
        data = response.json()
    except ValueError:
        data = None
    if not response.ok or data is None or (isinstance(data, dict) and data.get("success") is False):
        raise YoPrintError(response.status_code, data)
    return data
```

**PHP**

PHP 8 with Guzzle 7.

```php
<?php
use GuzzleHttp\Client;
use GuzzleHttp\Exception\RequestException;

const TEAM = 'your-team';

$client = new Client([
    'base_uri' => 'https://secure.yoprint.com',
    'timeout' => 30,
    'headers' => [
        'Authorization' => 'Bearer ' . getenv('YOPRINT_API_KEY'),
        'Content-Type' => 'application/json',
        'Accept' => 'application/json',
        'X-Yoprint-Decimal-Format' => 'exact-v1',
    ],
]);

function yoprint(Client $client, string $method, string $path, ?array $body = null): ?array
{
    $options = $body === null ? [] : ['json' => $body];
    try {
        $response = $client->request($method, str_replace('{team_slug}', TEAM, $path), $options);
    } catch (RequestException $e) {
        $status = $e->hasResponse() ? $e->getResponse()->getStatusCode() : 0;
        $error = $e->hasResponse() ? (string) $e->getResponse()->getBody() : $e->getMessage();
        throw new RuntimeException("YoPrint {$status}: {$error}", $status, $e);
    }
    if ($response->getStatusCode() === 204) {
        return null; // one delete answers with no body
    }
    $json = json_decode((string) $response->getBody(), true);
    if (!is_array($json) || (array_key_exists('success', $json) && $json['success'] === false)) {
        $status = $response->getStatusCode();
        throw new RuntimeException("YoPrint {$status}: " . (string) $response->getBody(), $status);
    }
    return $json;
}
```

## A read: list customers

`GET /v1/api/store/{team_slug}/customer` [List customers](https://developer.yoprint.com/reference/customers/v1-customer-index.md)

**JavaScript**

```javascript
const page = await yoprint('GET', '/v1/api/store/{team_slug}/customer?page=1');
for (const customer of page.data) {
  console.log(customer.id, customer.name);
}
console.log(`Page ${page.pagination.currentPage} of ${page.pagination.totalPages}`);
```

**Python**

```python
page = yoprint("GET", "/v1/api/store/{team_slug}/customer?page=1")
for customer in page["data"]:
    print(customer["id"], customer["name"])
print(f"Page {page['pagination']['currentPage']} of {page['pagination']['totalPages']}")
```

**PHP**

```php
$page = yoprint($client, 'GET', '/v1/api/store/{team_slug}/customer?page=1');
foreach ($page['data'] as $customer) {
    echo $customer['id'], ' ', $customer['name'], PHP_EOL;
}
echo "Page {$page['pagination']['currentPage']} of {$page['pagination']['totalPages']}", PHP_EOL;
```

## A write: create a customer

`POST /v1/api/store/{team_slug}/customer` [Create a customer](https://developer.yoprint.com/reference/customers/v1-customer-store.md)

A create isn't safe to repeat: sending it twice makes two customers. The examples don't retry it. See [Retries and duplicates](https://developer.yoprint.com/concepts/retries-and-duplicates.md).

**JavaScript**

```javascript
try {
  const created = await yoprint('POST', '/v1/api/store/{team_slug}/customer', {
    customer: { name: 'Northwind Apparel', tax_exempt: false },
    contact: [
      { first_name: 'Alex', last_name: 'Rivera', email: 'alex@example.com', send_invoice: true, is_primary: true },
    ],
  });
  console.log('Created', created.data.id);
} catch (error) {
  if (error.status === 422) {
    console.error('Fix these fields:', error.body?.error?.fields);
  } else {
    throw error;
  }
}
```

**Python**

```python
try:
    created = yoprint("POST", "/v1/api/store/{team_slug}/customer", {
        "customer": {"name": "Northwind Apparel", "tax_exempt": False},
        "contact": [{
            "first_name": "Alex",
            "last_name": "Rivera",
            "email": "alex@example.com",
            "send_invoice": True,
            "is_primary": True,
        }],
    })
    print("Created", created["data"]["id"])
except YoPrintError as error:
    if error.status == 422:
        print("Fix these fields:", (error.body or {}).get("error", {}).get("fields"))
    else:
        raise
```

**PHP**

```php
try {
    $created = yoprint($client, 'POST', '/v1/api/store/{team_slug}/customer', [
        'customer' => ['name' => 'Northwind Apparel', 'tax_exempt' => false],
        'contact' => [[
            'first_name' => 'Alex',
            'last_name' => 'Rivera',
            'email' => 'alex@example.com',
            'send_invoice' => true,
            'is_primary' => true,
        ]],
    ]);
    echo 'Created ', $created['data']['id'], PHP_EOL;
} catch (RuntimeException $e) {
    if ($e->getCode() === 422) {
        echo 'Fix the request: ', $e->getMessage(), PHP_EOL;
    } else {
        throw $e;
    }
}
```

## Check results that aren't errors

A few operations answer `200` without doing what you asked. Publishing an order is the main one: check `data.valid`.

```javascript
const published = await yoprint('PATCH', '/v2/api/store/{team_slug}/v2/sales_order/V4keXwwxMQyXw7/publish');
if (!published.data.valid) {
  throw new Error(`Not published: ${JSON.stringify(published.data.validation)}`);
}
```

See [Errors](https://developer.yoprint.com/concepts/errors.md#known-exceptions) for the others.

## Before you go further

- Read [Errors](https://developer.yoprint.com/concepts/errors.md) to handle each status properly.
- Read [Pagination](https://developer.yoprint.com/concepts/pagination.md) before you read whole lists.
- Read [What a write also does](https://developer.yoprint.com/concepts/side-effects.md) before you automate changes.
