# API reference

Base URL: `https://hubzone.app/api`

## Key

### Key information

`GET /open/v1/me`

Shows what the key may do and the limits per kind of call.

Permission: `any key`

**Example**

```bash
curl "https://hubzone.app/api/open/v1/me" \
  -H "Authorization: Bearer $HUBZONE_KEY"
```

**Responses**

- 200: The key.
- 401: No key, a wrong key, or a revoked or expired key.
- 429: Too many calls: wait for Retry-After.

```json
{
  "keyId": "3f0c8f0e-2b1a-4c55-9a38-0d6c1c9d7e11",
  "scopes": [
    "contacts.read",
    "contacts.write"
  ],
  "limits": {
    "get": {
      "label": "Read one record",
      "perMinute": 600
    }
  },
  "allScopes": 14
}
```

## Properties

### List properties

`GET /open/v1/properties/{type}`

The properties (fields) of a kind of record: their names, types and, for choice lists, the options. Use the names in create, change and search calls.

Permission: `{type}.read`

**Parameters**

- `type` (path, required, string): The kind of record. contacts, companies, deals, tickets, products, orders, calls.

**Example**

```bash
curl "https://hubzone.app/api/open/v1/properties/contacts" \
  -H "Authorization: Bearer $HUBZONE_KEY"
```

**Responses**

- 200: The properties.
- 401: No key, a wrong key, or a revoked or expired key.
- 403: The key lacks the permission for this call.
- 404: Not found (or it belongs to another workspace).
- 429: Too many calls: wait for Retry-After.

```json
{
  "properties": [
    {
      "key": "email",
      "type": "text",
      "label_i18n": {
        "en": "Email",
        "tr": "E-posta"
      },
      "options": null
    },
    {
      "key": "lifecycle_stage",
      "type": "enum",
      "label_i18n": {
        "en": "Lifecycle stage",
        "tr": "Yaşam döngüsü aşaması"
      },
      "options": [
        {
          "value": "lead",
          "label_i18n": {
            "en": "Lead"
          }
        }
      ]
    }
  ]
}
```

## Records

### List or find records

`GET /open/v1/objects/{type}`

Records of one kind, newest activity first, a page at a time. "q" finds records whose text properties contain the text. For the next page send the "after" value from the answer.

Permission: `{type}.read`

**Parameters**

- `type` (path, required, string): The kind of record. contacts, companies, deals, tickets, products, orders, calls.
- `limit` (query, integer 1-100): Records per page (default 25).
- `after` (query, string): The cursor of the next page, from the previous answer.
- `q` (query, string): Text to find.

**Example**

```bash
curl "https://hubzone.app/api/open/v1/objects/contacts?limit=25&q=ada" \
  -H "Authorization: Bearer $HUBZONE_KEY"
```

**Responses**

- 200: A page of records. "paging.next.after" is null on the last page.
- 400: The request is not valid; the message says why.
- 401: No key, a wrong key, or a revoked or expired key.
- 403: The key lacks the permission for this call.
- 429: Too many calls: wait for Retry-After.

```json
{
  "results": [
    {
      "id": "8a3b2f64-1c9d-4e05-b7a1-3f2c9d0e4a12",
      "objectType": "contacts",
      "properties": {
        "first_name": "Ada",
        "last_name": "Lovelace",
        "email": "ada@example.com"
      },
      "ownerId": "5d1f0f3e-7e0a-4b8e-9a50-aa14d3c9b2f1",
      "isPrivate": false,
      "createdAt": "2026-11-10T09:30:00.000Z",
      "updatedAt": "2026-11-10T09:30:00.000Z",
      "pipelineId": null,
      "stageId": null,
      "stageChangedAt": null,
      "lastActivityAt": null
    }
  ],
  "paging": {
    "next": {
      "after": "eyJ2IjoxfQ"
    }
  },
  "total": {
    "value": 1,
    "capped": false
  }
}
```

### Search records

`POST /open/v1/objects/{type}/search`

Search with filters, sorting and a text query. A record matches when it satisfies every filter in "filters". Operators: eq, neq, gt, gte, lt, lte, contains, starts_with, in, exists, not_exists.

Permission: `{type}.read`

**Parameters**

- `type` (path, required, string): The kind of record. contacts, companies, deals, tickets, products, orders, calls.

**Request body** (JSON): All fields are optional.

```json
{
  "query": "renewal",
  "filters": [
    {
      "property": "amount",
      "op": "gte",
      "value": 1000
    }
  ],
  "sort": {
    "property": "amount",
    "direction": "desc"
  },
  "limit": 25
}
```

**Example**

```bash
curl -X POST "https://hubzone.app/api/open/v1/objects/contacts/search" \
  -H "Authorization: Bearer $HUBZONE_KEY" \
  -H "content-type: application/json" \
  -d '{"query":"renewal","filters":[{"property":"amount","op":"gte","value":1000}],"sort":{"property":"amount","direction":"desc"},"limit":25}'
```

**Responses**

- 200: A page of records, as in the list call.
- 400: The request is not valid; the message says why.
- 401: No key, a wrong key, or a revoked or expired key.
- 403: The key lacks the permission for this call.
- 429: Too many calls: wait for Retry-After.

```json
{
  "results": [
    {
      "id": "8a3b2f64-1c9d-4e05-b7a1-3f2c9d0e4a12",
      "objectType": "contacts",
      "properties": {
        "first_name": "Ada",
        "last_name": "Lovelace",
        "email": "ada@example.com"
      },
      "ownerId": "5d1f0f3e-7e0a-4b8e-9a50-aa14d3c9b2f1",
      "isPrivate": false,
      "createdAt": "2026-11-10T09:30:00.000Z",
      "updatedAt": "2026-11-10T09:30:00.000Z",
      "pipelineId": null,
      "stageId": null,
      "stageChangedAt": null,
      "lastActivityAt": null
    }
  ],
  "paging": null,
  "total": {
    "value": 1,
    "capped": false
  }
}
```

### Read one record

`GET /open/v1/objects/{type}/{id}`

One record with all its properties.

Permission: `{type}.read`

**Parameters**

- `type` (path, required, string): The kind of record. contacts, companies, deals, tickets, products, orders, calls.
- `id` (path, required, string (uuid)): The id of the record.

**Example**

```bash
curl "https://hubzone.app/api/open/v1/objects/contacts/8a3b2f64-1c9d-4e05-b7a1-3f2c9d0e4a12" \
  -H "Authorization: Bearer $HUBZONE_KEY"
```

**Responses**

- 200: The record.
- 401: No key, a wrong key, or a revoked or expired key.
- 403: The key lacks the permission for this call.
- 404: Not found (or it belongs to another workspace).
- 429: Too many calls: wait for Retry-After.

```json
{
  "id": "8a3b2f64-1c9d-4e05-b7a1-3f2c9d0e4a12",
  "objectType": "contacts",
  "properties": {
    "first_name": "Ada",
    "last_name": "Lovelace",
    "email": "ada@example.com"
  },
  "ownerId": "5d1f0f3e-7e0a-4b8e-9a50-aa14d3c9b2f1",
  "isPrivate": false,
  "createdAt": "2026-11-10T09:30:00.000Z",
  "updatedAt": "2026-11-10T09:30:00.000Z",
  "pipelineId": null,
  "stageId": null,
  "stageChangedAt": null,
  "lastActivityAt": null
}
```

### Create a record

`POST /open/v1/objects/{type}`

Creates a record. Send properties under the names from "List properties". Only properties, ownerId, stageId and pipelineId are accepted. The key's person owns the record unless ownerId says otherwise. To log a phone call use the calls endpoint instead.

Permission: `{type}.write`

**Parameters**

- `type` (path, required, string): The kind of record. contacts, companies, deals, tickets, products, orders, calls.

**Request body** (JSON): "properties" is required.

```json
{
  "properties": {
    "first_name": "Ada",
    "last_name": "Lovelace",
    "email": "ada@example.com"
  }
}
```

**Example**

```bash
curl -X POST "https://hubzone.app/api/open/v1/objects/contacts" \
  -H "Authorization: Bearer $HUBZONE_KEY" \
  -H "content-type: application/json" \
  -d '{"properties":{"first_name":"Ada","last_name":"Lovelace","email":"ada@example.com"}}'
```

**Responses**

- 201: The new record.
- 400: The request is not valid; the message says why.
- 401: No key, a wrong key, or a revoked or expired key.
- 403: The key lacks the permission for this call.
- 429: Too many calls: wait for Retry-After.

```json
{
  "id": "8a3b2f64-1c9d-4e05-b7a1-3f2c9d0e4a12",
  "objectType": "contacts",
  "properties": {
    "first_name": "Ada",
    "last_name": "Lovelace",
    "email": "ada@example.com"
  },
  "ownerId": "5d1f0f3e-7e0a-4b8e-9a50-aa14d3c9b2f1",
  "isPrivate": false,
  "createdAt": "2026-11-10T09:30:00.000Z",
  "updatedAt": "2026-11-10T09:30:00.000Z",
  "pipelineId": null,
  "stageId": null,
  "stageChangedAt": null,
  "lastActivityAt": null
}
```

### Change a record

`PATCH /open/v1/objects/{type}/{id}`

Changes the properties you send; the others stay as they are. Send a property as null to clear it. A deal or ticket moves to another stage with stageId.

Permission: `{type}.write`

**Parameters**

- `type` (path, required, string): The kind of record. contacts, companies, deals, tickets, products, orders, calls.
- `id` (path, required, string (uuid)): The id of the record.

**Request body** (JSON): Send only what changes.

```json
{
  "properties": {
    "job_title": "Mathematician"
  }
}
```

**Example**

```bash
curl -X PATCH "https://hubzone.app/api/open/v1/objects/contacts/8a3b2f64-1c9d-4e05-b7a1-3f2c9d0e4a12" \
  -H "Authorization: Bearer $HUBZONE_KEY" \
  -H "content-type: application/json" \
  -d '{"properties":{"job_title":"Mathematician"}}'
```

**Responses**

- 200: The changed record.
- 400: The request is not valid; the message says why.
- 401: No key, a wrong key, or a revoked or expired key.
- 403: The key lacks the permission for this call.
- 404: Not found (or it belongs to another workspace).
- 429: Too many calls: wait for Retry-After.

```json
{
  "id": "8a3b2f64-1c9d-4e05-b7a1-3f2c9d0e4a12",
  "objectType": "contacts",
  "properties": {
    "first_name": "Ada",
    "last_name": "Lovelace",
    "email": "ada@example.com",
    "job_title": "Mathematician"
  },
  "ownerId": "5d1f0f3e-7e0a-4b8e-9a50-aa14d3c9b2f1",
  "isPrivate": false,
  "createdAt": "2026-11-10T09:30:00.000Z",
  "updatedAt": "2026-11-10T09:30:00.000Z",
  "pipelineId": null,
  "stageId": null,
  "stageChangedAt": null,
  "lastActivityAt": null
}
```

## Calls

### Log a phone call

`POST /open/v1/calls`

For phones and phone systems: the call is tied to the contact with that phone number and shows on their timeline. Audio is never sent. With externalId a call you send twice is stored once.

Permission: `calls.write`

**Request body** (JSON): phoneNumber is required. direction: inbound or outbound. outcome: busy, connected, left_live_message, left_voicemail, meeting_booked, no_answer or wrong_number.

```json
{
  "phoneNumber": "+90 532 000 00 00",
  "direction": "outbound",
  "startedAt": "2026-11-10T09:30:00Z",
  "durationSeconds": 184,
  "outcome": "connected",
  "note": "Asked for a quote",
  "externalId": "pbx-7781"
}
```

**Example**

```bash
curl -X POST "https://hubzone.app/api/open/v1/calls" \
  -H "Authorization: Bearer $HUBZONE_KEY" \
  -H "content-type: application/json" \
  -d '{"phoneNumber":"+90 532 000 00 00","direction":"outbound","startedAt":"2026-11-10T09:30:00Z","durationSeconds":184,"outcome":"connected","note":"Asked for a quote","externalId":"pbx-7781"}'
```

**Responses**

- 201: The call and the contact it was tied to (linked is false when no contact has that number).
- 400: The request is not valid; the message says why.
- 401: No key, a wrong key, or a revoked or expired key.
- 403: The key lacks the permission for this call.
- 429: Too many calls: wait for Retry-After.

```json
{
  "id": "c1d2e3f4-0000-4000-8000-000000000001",
  "activityId": "a1b2c3d4-0000-4000-8000-000000000002",
  "contactId": "8a3b2f64-1c9d-4e05-b7a1-3f2c9d0e4a12",
  "linked": true,
  "duplicate": false
}
```

## Events

### Send a custom event

`POST /public/events/{eventKey}`

Records one occurrence of a custom event you defined. No Authorization header: the event key in the address is the credential. See the Custom events guide.

Permission: `event key` (no Authorization header)

**Parameters**

- `eventKey` (path, required, string): The key of the event (starts with hze_), shown once when you make the event.

**Request body** (JSON): email is required.

```json
{
  "email": "person@example.com",
  "properties": {
    "plan": "pro",
    "seats": 5
  },
  "occurredAt": "2026-11-10T09:30:00Z"
}
```

**Example**

```bash
curl -X POST "https://hubzone.app/api/public/events/hze_your_event_key" \
  -H "content-type: application/json" \
  -d '{"email":"person@example.com","properties":{"plan":"pro","seats":5},"occurredAt":"2026-11-10T09:30:00Z"}'
```

**Responses**

- 201: Stored. linkedToContact says whether a contact has that e-mail address.
- 400: The request is not valid; the message says why.
- 404: Unknown event key.
- 429: Too many calls: wait for Retry-After.

```json
{
  "id": "1b7e0c52-6f0e-4c7a-9a43-2d5e9d1f0a77",
  "linkedToContact": true
}
```
