# Oktavia Connect Developer Guide

Oktavia Connect is the integration layer for POS, payroll, accounting, HR and operational systems that need to exchange data with Oktavia.

The product goal is simple: connect sales, schedules, time tracking and payroll without spreadsheets, duplicate entry or opaque manual changes.

## Surfaces

| Surface | User | Purpose |
| --- | --- | --- |
| Oktavia app | Owner, admin, implementation support | Create connections, map centers, rotate credentials, review errors |
| Developer portal | POS, payroll providers, agencies, integrators | Read contracts, test sandbox payloads, download OpenAPI and collections |
| Connect API | External systems | Send and receive signed operational data |
| Public status | Customers and partners | Check API, webhook and platform availability |

## Core Concepts

### Connection

A connection represents an external system authorized by a customer. It has its own environment, credentials, scopes, status and event history.

Typical connection types:

- POS inbound: daily close, interval sales, products, reservations.
- Payroll outbound: validated hours, approved absences, incidents.
- Bidirectional: system sends demand data and receives schedule publication events.

### Environment

Use separate credentials for sandbox and production. Sandbox must be used before enabling production.

### Scopes

Scopes limit exactly what a connection can read or write. Examples:

- `pos:daily-close:write`
- `pos:sales:write`
- `payroll:hours:read`
- `payroll:absences:read`
- `webhooks:receive`

### Event Log

Every inbound or outbound event is logged with status, payload hash, timestamps, retry count and normalized summary.

## Customer Partner Authorization Model

Partners do not receive global access to Oktavia customers.

The recommended flow is:

1. The partner implements and tests against sandbox.
2. The Oktavia customer creates a connection from their account.
3. Oktavia generates credentials for that connection only.
4. The customer shares the technical package with the partner.
5. The partner sends or receives signed events using the authorized scopes.
6. The customer can pause, revoke or rotate credentials at any time.

Production credentials are always customer-issued and scoped. They are bound to:

- one customer organization,
- one connection,
- one environment,
- selected scopes,
- the configured provider and direction.

There is no global partner API key that grants access to all customers. This keeps authorization explicit, auditable and revocable.

## Inbound Events

Endpoint:

```http
POST /connect/inbound/{connectionId}/events
```

Required headers:

```http
content-type: application/json
x-oktavia-key: ok_live_...
x-oktavia-timestamp: 1780300800000
x-oktavia-signature: <hmac-sha256>
x-idempotency-key: close-2026-06-01-almacen-sur
```

Call this endpoint whenever the external system creates a new operational event.

Send every inbound event with the same envelope:

| Field | Use | Notes |
| --- | --- | --- |
| `eventType` | Required | Functional type. Example: `pos.daily_close` |
| `externalId` | Recommended | Stable identifier in the source system |
| `occurredAt` | Recommended | Real event timestamp in ISO 8601. If missing, Oktavia uses receipt time |
| `payload` | Required | Event-specific object |

Keep the envelope stable. Shape `payload` according to `eventType`. Include all minimum fields. Put provider-specific fields inside `payload`; Oktavia treats them as extensions until a connection mapping explicitly uses them.

| Event type | Purpose | Minimum payload | Optional examples |
| --- | --- | --- | --- |
| `pos.daily_close` | Daily POS close by center and operating date | `date`, `center`, `total`, `currency` | `channels`, `taxes`, `ticketCount`, `serviceBreakdown` |
| `pos.sales_interval` | Sales aggregated by time interval | `date`, `center`, `intervals[]` | `intervals[].food`, `intervals[].drinks`, `intervals[].tickets` |
| `pos.products` | Product or category sales | `date`, `center`, `items[]` | `items[].category`, `items[].quantity`, `items[].grossTotal` |
| `reservations.forecast` | Reservations or expected covers by time slot | `date`, `center`, `slots[]` | `slots[].covers`, `slots[].status`, `slots[].source` |

Do not use arbitrary JSON as the integration contract. If the provider needs an unlisted field, send it under `payload` and document the mapping during connection certification.

Example POS daily close:

```json
{
  "eventType": "pos.daily_close",
  "externalId": "close-2026-06-01-centro-principal",
  "occurredAt": "2026-06-01T23:05:00.000Z",
  "payload": {
    "date": "2026-06-01",
    "center": "Centro Principal",
    "total": 4180.5,
    "currency": "EUR",
    "channels": [
      { "name": "Comida", "total": 2660.1 },
      { "name": "Bebida", "total": 1520.4 }
    ]
  }
}
```

Example interval sales:

```json
{
  "eventType": "pos.sales_interval",
  "externalId": "sales-2026-06-01-centro-principal-15-00",
  "occurredAt": "2026-06-01T15:05:00.000Z",
  "payload": {
    "date": "2026-06-01",
    "center": "Centro Principal",
    "intervals": [
      { "start": "13:00", "end": "14:00", "total": 910.2, "food": 620.1, "drinks": 290.1, "tickets": 34 },
      { "start": "14:00", "end": "15:00", "total": 1240.4, "food": 870.2, "drinks": 370.2, "tickets": 41 }
    ],
    "currency": "EUR"
  }
}
```

Accepted response:

```json
{
  "id": "evt_01J...",
  "status": "accepted",
  "eventType": "pos.daily_close",
  "payloadHash": "sha256_hash",
  "duplicate": false
}
```

## Try It Sandbox

The developer portal includes a browser-based sandbox console. It signs the request locally and sends the event to the Oktavia Connect API.

You need:

- API base URL: `https://api.oktavia.app`.
- `connectionId` generated by the customer from Oktavia Connect.
- Sandbox API key generated for that connection.
- Sandbox secret generated for that connection.
- Idempotency key.
- Raw JSON body.

The customer creates the sandbox connection in Oktavia Connect and shares the technical package with the partner. The secret is used in the browser only to calculate HMAC-SHA256. Do not use production credentials on shared machines.

## Signature

Oktavia signs and verifies payloads with HMAC-SHA256.

Signed payload:

```text
<timestamp>.<raw-json-body>
```

Node.js verification:

```js
import crypto from 'node:crypto';

function verifyOktaviaSignature(secret, timestamp, body, signature) {
  const expected = crypto
    .createHmac('sha256', secret)
    .update(`${timestamp}.${body}`)
    .digest('hex');

  return crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(signature));
}
```

Important:

- Sign the exact raw JSON body that is sent.
- Reject old timestamps to reduce replay risk.
- Do not expose secrets in browsers, mobile apps or public repositories.

## Idempotency

Use `x-idempotency-key` for all retryable inbound events.

Good keys:

- `close-2026-06-01-almacen-sur`
- `sales-2026-06-01-15-00-almacen-sur`
- `products-2026-06-01-pos-terminal-2`

If the same idempotency key is received with the same payload, Oktavia treats it as a duplicate. If the same key is received with a different payload, Oktavia returns a conflict.

## HTTP Responses

| Code | Meaning |
| --- | --- |
| 202 | Event accepted |
| 200 | Duplicate event recognized |
| 400 | Invalid payload |
| 401 | Invalid API key, timestamp or signature |
| 403 | Scope missing or connection inactive |
| 409 | Idempotency conflict |
| 429 | Rate limit exceeded |
| 5xx | Temporary server error; retry with backoff |

## Outbound Webhooks

Oktavia can send signed webhook events to external systems.

Common events:

- `schedule.published`
- `time_entry.validated`
- `absence.approved`
- `absence.rejected`
- `payroll.hours_export`
- `payroll.absences_export`

Webhook headers:

| Header | Meaning |
| --- | --- |
| `x-oktavia-event` | Event type |
| `x-oktavia-signature` | HMAC-SHA256 signature |
| `x-oktavia-timestamp` | Timestamp used for signing |
| `x-oktavia-delivery-id` | Delivery identifier for support |

Example:

```json
{
  "type": "schedule.published",
  "occurredAt": "2026-06-01T08:00:00.000Z",
  "data": {
    "scheduleId": "schedule_id",
    "centerId": "center_id",
    "weekStart": "2026-06-15",
    "weekEnd": "2026-06-21"
  }
}
```

## Payroll And Agencies

Payroll connections are outbound from Oktavia. The customer controls the connection, scopes and period to export. External payroll systems, agencies or ERPs receive validated labor data instead of raw drafts.

Available exports:

| Export | Endpoint | Scope |
| --- | --- | --- |
| Validated hours | `POST /organizations/{organizationId}/connect/connections/{connectionId}/payroll-hours-export` | `payroll:hours:read` |
| Approved absences | `POST /organizations/{organizationId}/connect/connections/{connectionId}/payroll-absences-export` | `payroll:absences:read` |

Request body:

```json
{
  "startDate": "2026-05-01",
  "endDate": "2026-05-31"
}
```

Validated hours payload:

```json
{
  "format": "oktavia.payroll.hours.v1",
  "period": {
    "startDate": "2026-05-01",
    "endDate": "2026-05-31"
  },
  "rows": [
    {
      "employeeCode": "employee-001",
      "employeeName": "Empleado 001",
      "center": "Centro Principal",
      "ordinaryMinutes": 9120,
      "complementaryMinutes": 0,
      "overtimeMinutes": 120,
      "nightMinutes": 90,
      "holidayMinutes": 0
    }
  ]
}
```

Approved absences payload:

```json
{
  "format": "oktavia.payroll.absences.v1",
  "period": {
    "startDate": "2026-05-01",
    "endDate": "2026-05-31"
  },
  "rows": [
    {
      "employeeCode": "employee-003",
      "employeeName": "Empleado 003",
      "type": "vacation",
      "startDate": "2026-05-20",
      "endDate": "2026-05-24",
      "approvedAt": "2026-05-12T10:30:00.000Z"
    }
  ]
}
```

Implementation notes:

- Export only validated hours when possible.
- Keep the payroll period explicit.
- Store the generated event or delivery ID for later audit.
- Do not use payroll exports as the source for operational schedule edits.

## Operational Guidance

Before going live:

- Use sandbox credentials first.
- Validate center mapping with the customer.
- Store request IDs, payload hashes and error responses.
- Implement retries with backoff for 5xx and timeouts.
- Deduplicate inbound and outbound events.
- Rotate credentials if they are shared incorrectly.

Support requests should include:

- `connectionId`
- environment
- timestamp in UTC
- eventType
- externalId
- idempotency key
- deliveryId or requestId
- HTTP status
- payloadHash

Do not send secrets or full real employee documents in tickets.
