XONIKA3 API

Read and change your CRM data from your own systems. Base address: https://crm.xonik.com/api/v1. Machine-readable description: openapi.json.

Keys

In XONIKA3 go to Admin → Developers, create a key and choose what it may do. The key is shown once. Send it on every request:

curl https://crm.xonik.com/api/v1/crm/contacts \
  -H "Authorization: Bearer xk_live_…"

A key can never do more than the person who made it can do today, and it stops working if they leave the organization. Keys cannot sign in, manage people, billing, integrations or other keys. Limit: 600 requests a minute per key.

PermissionAllows
crm.contact:readRead contacts and companies
crm.contact:createCreate contacts and companies
crm.contact:updateUpdate contacts and companies
crm.deal:readRead deals, products, tasks and notes
crm.deal:createCreate deals, tasks and notes
crm.deal:updateUpdate deals and tasks
prospecting.lead:readRead leads
prospecting.lead:createCreate leads
support.ticket:readRead tickets
support.ticket:createCreate tickets
support.ticket:updateReply to and update tickets
invoicing.invoice:readRead invoices and payments
invoicing.invoice:createCreate invoices
commerce.order:readRead orders
commerce.order:createCreate orders
booking.reservation:readRead bookings

Conventions

Endpoints

GET /crm/contacts

List contacts · permission crm.contact:read

Query: search Name, email or company · page Page number · limit Per page (max 100)

POST /crm/contacts

Create a contact · permission crm.contact:create

Body
{
  "first_name": {
    "type": "string"
  },
  "last_name": {
    "type": "string"
  },
  "email": {
    "type": "string",
    "format": "email"
  },
  "phone": {
    "type": "string"
  },
  "company_name": {
    "type": "string"
  },
  "title": {
    "type": "string"
  },
  "city": {
    "type": "string"
  },
  "country": {
    "type": "string"
  },
  "owner_id": {
    "type": "string",
    "format": "uuid"
  },
  "custom_fields": {
    "type": "object"
  }
}

Required: first_name

GET /crm/contacts/{id}

Get a contact · permission crm.contact:read

PATCH /crm/contacts/{id}

Update a contact (only the fields sent) · permission crm.contact:update

Body
{
  "first_name": {
    "type": "string"
  },
  "last_name": {
    "type": "string"
  },
  "email": {
    "type": "string"
  },
  "phone": {
    "type": "string"
  },
  "title": {
    "type": "string"
  },
  "company_name": {
    "type": "string"
  },
  "status": {
    "type": "string"
  },
  "lifecycle_stage": {
    "type": "string"
  },
  "owner_id": {
    "type": "string",
    "format": "uuid"
  }
}

GET /crm/companies

List companies · permission crm.contact:read

POST /crm/companies

Create a company · permission crm.contact:create

Body
{
  "name": {
    "type": "string"
  },
  "domain": {
    "type": "string"
  },
  "industry": {
    "type": "string"
  },
  "size": {
    "type": "string"
  },
  "country": {
    "type": "string"
  },
  "city": {
    "type": "string"
  }
}

Required: name

PATCH /crm/companies/{id}

Update a company · permission crm.contact:update

Body
{
  "name": {
    "type": "string"
  },
  "domain": {
    "type": "string"
  },
  "industry": {
    "type": "string"
  },
  "tier": {
    "type": "string"
  }
}

GET /crm/deals

List deals · permission crm.deal:read

Query: stage lead, qualified, proposal, negotiation, won, lost · owner_id Owner

POST /crm/deals

Create a deal · permission crm.deal:create

Body
{
  "title": {
    "type": "string"
  },
  "value_cents": {
    "type": "integer",
    "description": "Amount in the smallest unit, e.g. 150000 = 1,500.00"
  },
  "currency": {
    "type": "string",
    "description": "ISO code; default: the organization's"
  },
  "stage": {
    "type": "string"
  },
  "contact_id": {
    "type": "string",
    "format": "uuid"
  },
  "company_id": {
    "type": "string",
    "format": "uuid"
  },
  "owner_id": {
    "type": "string",
    "format": "uuid"
  },
  "expected_close_date": {
    "type": "string",
    "format": "date-time"
  }
}

Required: title, value_cents

GET /crm/deals/{id}

Get a deal · permission crm.deal:read

POST /crm/deals/{id}/move

Move a deal to another stage (lost needs loss_reason) · permission crm.deal:update

Body
{
  "stage_key": {
    "type": "string",
    "description": "A stage key of the deal's pipeline, e.g. won"
  },
  "pipeline_id": {
    "type": "string",
    "format": "uuid"
  },
  "loss_reason": {
    "type": "string",
    "description": "price, feature_gap, competitor, timing, …"
  }
}

Required: stage_key

GET /crm/products

List products · permission crm.deal:read

GET /crm/tasks

List follow-ups · permission crm.deal:read

POST /crm/tasks

Create a follow-up · permission crm.deal:create

Body
{
  "title": {
    "type": "string"
  },
  "due_date": {
    "type": "string",
    "format": "date-time"
  },
  "priority": {
    "type": "string",
    "description": "low, medium, high, urgent"
  },
  "contact_id": {
    "type": "string",
    "format": "uuid"
  },
  "deal_id": {
    "type": "string",
    "format": "uuid"
  },
  "assigned_to": {
    "type": "string",
    "format": "uuid"
  }
}

Required: title

POST /crm/notes

Add a note to a contact or deal · permission crm.deal:create

Body
{
  "content": {
    "type": "string"
  },
  "contact_id": {
    "type": "string",
    "format": "uuid"
  },
  "deal_id": {
    "type": "string",
    "format": "uuid"
  }
}

Required: content

GET /prospecting/leads

List leads · permission prospecting.lead:read

POST /prospecting/leads

Create a lead · permission prospecting.lead:create

Body
{
  "company_name": {
    "type": "string"
  },
  "contact_name": {
    "type": "string"
  },
  "email": {
    "type": "string"
  },
  "domain": {
    "type": "string"
  },
  "score": {
    "type": "integer",
    "description": "0-100"
  }
}

Required: company_name

GET /support/tickets

List tickets · permission support.ticket:read

Query: status open, pending, resolved, closed · priority low, medium, high, urgent

POST /support/tickets

Open a ticket · permission support.ticket:create

Body
{
  "subject": {
    "type": "string"
  },
  "description": {
    "type": "string"
  },
  "priority": {
    "type": "string"
  },
  "customer_email": {
    "type": "string",
    "description": "Matched to (or added as) a contact"
  },
  "customer_id": {
    "type": "string",
    "format": "uuid"
  }
}

Required: subject, description

GET /support/tickets/{id}

Get a ticket with its messages · permission support.ticket:read

POST /support/tickets/{id}/messages

Add a reply or internal note · permission support.ticket:update

Body
{
  "body": {
    "type": "string"
  },
  "is_internal_note": {
    "type": "boolean"
  }
}

Required: body

GET /invoicing/invoices

List invoices · permission invoicing.invoice:read

Query: status draft, issued, partially_paid, paid, credited, void

POST /invoicing/invoices

Create an invoice (issued unless status is draft) · permission invoicing.invoice:create

Body
{
  "customer_name": {
    "type": "string"
  },
  "customer_email": {
    "type": "string"
  },
  "customer_contact_id": {
    "type": "string",
    "format": "uuid"
  },
  "currency": {
    "type": "string"
  },
  "due_at": {
    "type": "string",
    "format": "date-time"
  },
  "status": {
    "type": "string",
    "description": "draft or issued"
  },
  "items": {
    "type": "array",
    "items": {
      "type": "object",
      "properties": {
        "description": {
          "type": "string"
        },
        "quantity": {
          "type": "integer"
        },
        "unit_price_cents": {
          "type": "integer"
        },
        "tax_rate_percent": {
          "type": "number"
        }
      },
      "required": [
        "description",
        "quantity",
        "unit_price_cents"
      ]
    }
  }
}

Required: items

GET /invoicing/invoices/{id}

Get an invoice with lines, payments, credit notes and refunds · permission invoicing.invoice:read

GET /commerce/orders

List orders · permission commerce.order:read

POST /commerce/orders

Create an order · permission commerce.order:create

Body
{
  "customer_id": {
    "type": "string",
    "format": "uuid"
  },
  "items": {
    "type": "array",
    "items": {
      "type": "object",
      "properties": {
        "description": {
          "type": "string"
        },
        "quantity": {
          "type": "integer"
        },
        "unit_price_cents": {
          "type": "integer"
        },
        "product_id": {
          "type": "string",
          "format": "uuid"
        }
      },
      "required": [
        "description",
        "quantity",
        "unit_price_cents"
      ]
    }
  },
  "tax_rate_percent": {
    "type": "number"
  },
  "discount_code": {
    "type": "string"
  }
}

Required: items

GET /commerce/orders/{id}

Get an order · permission commerce.order:read

Webhooks

In Admin → Developers → Webhooks add an https address and choose events. Each delivery is a POST with JSON:

{ "id": "evt_…", "type": "deal.won", "created_at": "2026-10-28T10:00:00Z", "data": { "object": "deal", "id": "…", "title": "…", "value_cents": 150000, … } }

Check it came from XONIKA3: the header X-Xonika-Signature: t=<unix time>,v1=<hex> is the HMAC-SHA256 of <t>.<raw body> with your webhook's signing secret. Reject old timestamps (over 5 minutes). Answer with any 2xx within 10 seconds; otherwise it is retried after 1 min, 5 min, 30 min, 2 h, 12 h and 24 h. The same event may arrive twice: use id to ignore repeats. A webhook that fails 25 times in a row is paused (and the team is told).

// Node.js
const [t, v1] = header.split(',').map((p) => p.split('=')[1]);
const expected = crypto.createHmac('sha256', secret).update(t + '.' + rawBody).digest('hex');
const valid = crypto.timingSafeEqual(Buffer.from(v1), Buffer.from(expected)) && Math.abs(Date.now() / 1000 - Number(t)) < 300;
EventWhendata.object
contact.createdA contact is addedcontact
contact.updatedA contact is changedcontact
deal.createdA deal is addeddeal
deal.stage_changedA deal moves stagedeal
deal.wonA deal is wondeal
deal.lostA deal is lostdeal
lead.createdA lead is addedlead
ticket.createdA ticket is openedticket
ticket.resolvedA ticket is resolved or closedticket
invoice.issuedAn invoice is issuedinvoice
invoice.paidAn invoice is paid in fullinvoice
invoice.overdueAn invoice becomes overdueinvoice
payment.receivedA payment is recordedinvoice
order.createdAn order is placedorder
order.paidAn order is paidorder
booking.createdA meeting is bookedbooking
booking.cancelledA meeting is cancelledbooking
form.submittedA form is submittedform_submission