# yofi document api overview

Companion to `gitbook-document-api.yaml`. Upload this file to GitBook as a normal page; keep both in sync when behavior changes.

REST API for document creation, listing, metadata updates, and upload URL management.

**Base URL:** The Documents API is mounted under the `/v1` path prefix on the Yofi Portal public API host (see **Servers** in the OpenAPI spec). Paths in the spec are relative to that base.

All endpoints follow JSON:API conventions. `mime_type` is never accepted in requests — it is determined server-side via magic byte inspection after upload (partners do not call a separate finalize endpoint).

## Authentication

All requests require `Authorization: Bearer <JWT>`. Supported token types depend on your integration (for example Okta, Shopify session tokens, and integration HS256 JWTs).

**Credentials are provisioned per client on demand.** To get started:

1. Contact the Yofi team to request API access
2. You will receive credentials for token generation and, when applicable, one or more **integration IDs** for your organization
3. Integration IDs for storage-based file transfers (`cloud_storage_gcs`, `cloud_storage_s3`) are provisioned separately upon request

These credentials are unique to your organization and must not be shared.

### Integration JWT (yofi-data)

For server-to-server access, sign an **HS256** JWT with `iss` = `yofi-data`, `sub` = `yofi_data_admin`, `aud` as a **JSON array** of strings that includes `yofi-public-api`, `client_id` = `backend-service`, and a unique `jti` on every token. Include standard `iat` and `exp`. Use the HMAC secret from the AWS Secrets Manager JSON `PARTNER_WEBHOOK_SECRETS` under the key **`yofi_data`**.

**Important:** `aud` must be a JSON **array** (e.g. `["yofi-public-api"]`). A single string for `aud` is rejected by the API. After verification, `partner_id`, `organization_id`, and `app_id` headers must match your provisioned employee / data-permission records or the API returns **403 Forbidden**.

#### Example claims (before signing)

```json
{
  "iss": "yofi-data",
  "sub": "yofi_data_admin",
  "aud": ["yofi-public-api"],
  "client_id": "backend-service",
  "jti": "550e8400-e29b-41d4-a716-446655440000",
  "iat": 1712090000,
  "exp": 1712090300
}
```

#### Python (PyJWT)

```python
import os
import time
import uuid
import jwt

# Same string as PARTNER_WEBHOOK_SECRETS["yofi_data"] (provided by Yofi)
secret = os.environ["YOFI_DATA_HMAC_SECRET"]
now = int(time.time())
payload = {
    "iss": "yofi-data",
    "sub": "yofi_data_admin",
    "aud": ["yofi-public-api"],
    "client_id": "backend-service",
    "jti": str(uuid.uuid4()),
    "iat": now,
    "exp": now + 300,
}
token = jwt.encode(payload, secret, algorithm="HS256")
# Header: Authorization: Bearer <token>
```

#### Node.js (jsonwebtoken)

```javascript
const jwt = require("jsonwebtoken");
const { randomUUID } = require("crypto");

const secret = process.env.YOFI_DATA_HMAC_SECRET;
const now = Math.floor(Date.now() / 1000);
const token = jwt.sign(
  {
    iss: "yofi-data",
    sub: "yofi_data_admin",
    aud: ["yofi-public-api"],
    client_id: "backend-service",
    jti: randomUUID(),
    iat: now,
    exp: now + 300,
  },
  secret,
  { algorithm: "HS256" }
);
// Header: Authorization: Bearer <token>
```

Use a **new `jti`** for each issued token. Keep `exp` short (for example a few minutes) unless your security review says otherwise.

## Tenant context

Send tenant scope using these headers (lowercase names as used by the API):

* `partner_id`
* `organization_id`
* `app_id` (for Shopify, typically the `*.myshopify.com` shop domain)

The same values may be sent with `X-` prefixes (for example `X-Organization-Id`); header names are case-insensitive. Your account manager will confirm which headers are required for your setup.

## MIME type handling

`mime_type` is intentionally **not accepted** in any request. Client-declared MIME types are untrustworthy (a renamed executable can claim `image/jpeg`). The actual MIME type is detected server-side from the file's magic bytes after upload and validated against an allowlist (images, PDFs, common document formats). Disallowed file types are rejected automatically.
