Inside the Monesize Desk Public API
Monesize Desk was originally built to be a product people use directly. Then we started thinking about what would happen if we stopped treating the Desk interface as the product. Monesize Desk has always been a single-tenant-isolated support platform. Every row in the database is scoped to an organization, and every agent-facing endpoint authenticates through a session cookie. That model is great for a hosted workspace, but it does not help an organization that wants to embed support inside its own product.
The public API extension changes that. It exposes the same support infrastructure Desk already provides, through a machine-to-machine interface that products can call directly.
This post walks through the design: the key model, environment isolation, authentication middleware, the request surface, and the failure modes. It's a deep look at how the extension is built on top of the existing Desk core.
The Core Idea
Desk now supports two consumption models.
The Desk portal is the hosted experience. Customers visit a portal, open tickets, reply, and read knowledge base articles. Agents work the queue in the Desk workspace.
The Desk API is for organizations that want to build their own customer-facing support experience. A company embeds a Help section in its product. The product calls the Desk API. Customers, tickets, conversations, attachments, status, priority, teams, assignments, escalations, and history are all managed through the API. Agents keep working in the normal Desk workspace, and the customer never needs to know Desk is involved.
The design constraint was important: do not invent capabilities Desk does not have. The API surface is a projection of existing Desk concepts onto an HTTP interface.
Architecture
The API sits in front of the existing service layer, not alongside a copy of it. A request from an organization's product arrives at the Desk API carrying a bearer key. That request flows through a new auth middleware, which resolves the key down to a tenant and an environment, and then delegates to the exact same services the agent-facing routes use: tickets, customers, teams, the knowledge base, attachments. Those services are the same ones the agent workspace itself reads from and writes to.
Because the request lands on the same service layer rather than a parallel copy of it, every rule already enforced in the agent UI, such as status transitions and customer-reopen behavior, is enforced on the public API with no duplication.
API Keys
API keys are per-organization credentials with an environment attached.
Each key row stores four meaningful fields: a name, an environment, a SHA-256 hash of the secret, and a display prefix. The plaintext secret is shown exactly once, at creation time, and is never persisted. The prefix, such as dsk_live_01234567, lets support engineers recognize the key in logs without ever exposing the secret.
The key format is self-describing. Test keys begin with dsk_test_. Production keys begin with dsk_live_. The prefix routes the request semantically before any database lookup, and it makes it immediately obvious which environment a key belongs to.
Keys are created, listed, and revoked under /v1/api-keys. These routes require an authenticated agent session with the ADMIN role. Regular authenticated users can read the developer documentation, but they cannot create, view, or revoke keys. This matches the requirement that key management stays in the hands of administrators.
When a key is revoked, its revokedAt timestamp is set, and every subsequent request that presents it is rejected with API_KEY_INVALID.
Test and Production Isolation
The most interesting part of the design is how test and production data stay separate.
The requirement is strict: the agent workspace operates against production only, and test API activity must never appear in it. Two keys for the same organization must not write into the same data.
The solution is a shadow tenant. When an administrator creates the first test key for an organization, the service lazily provisions a hidden organization row. That row carries isTestEnvironment = true and a parentTenantId pointing back to the real organization. Its portal is disabled, it has no slug, and it has no users. It cannot be reached through the portal, and it cannot be logged into.
The test key is bound to the shadow tenant. The production key is bound to the real tenant. Because every service already scopes every query by tenantId, isolation is structural rather than enforced by ad hoc filters. The agent workspace queries the real tenant. Test requests resolve to the shadow tenant. There is no code path where the two can mix, because the tenant id itself is the partition boundary.
The practical effect: a developer can run integration tests, create customers, open tickets, and post replies, and the agent queue stays clean. When the test suite runs again, the shadow tenant is already there and is simply reused.
Authentication Middleware
Public API requests authenticate with a bearer key:
Authorization: Bearer dsk_live_0123456789abcdefThe X-Desk-Api-Key header is accepted as an alternative for clients that cannot set an Authorization header easily.
The middleware hashes the presented key and looks it up by keyHash. It rejects the request with a 401 API_KEY_INVALID when the key is missing, unknown, or revoked. On success it sets the request's tenantId, which is the shadow tenant for test keys and the real tenant for production keys. The rest of the request then behaves like any other tenant-scoped request.
A last-used timestamp is updated asynchronously after each successful lookup. That gives organizations visibility into whether a key is still being used, without adding a write to the request hot path.
The public API has its own rate limiter: 300 requests per minute per key. Beyond that, requests return 429 RATE_LIMITED.
Request Surface
The API is mounted at /v1/api and mirrors existing Desk concepts.
Customers
GET /v1/api/customers?q=&page=&perPage=
POST /v1/api/customers
GET /v1/api/customers/:customerId
PATCH /v1/api/customers/:customerIdCustomers can be searched, created, fetched with their full ticket history, and updated.
Tickets
GET /v1/api/tickets?status=&priority=&category=&teamId=&agentId=&unassigned=
POST /v1/api/tickets
GET /v1/api/tickets/:ticketId
PATCH /v1/api/tickets/:ticketId
POST /v1/api/tickets/:ticketId/assign
POST /v1/api/tickets/:ticketId/reopen
POST /v1/api/tickets/:ticketId/escalate
POST /v1/api/tickets/:ticketId/messagesCreating a ticket takes a customer email plus an optional customer name. The service finds the customer by email, or creates them when they do not exist, then opens the ticket on their behalf. The list endpoint supports the same queue-style filters as the agent inbox, including an unassigned flag.
Updating a ticket follows the same status rules as the agent UI. Setting status to RESOLVED stamps resolvedAt. Setting it to CLOSED stamps closedAt. Moving a resolved or closed ticket back to OPEN records a reopen.
Conversations
POST /v1/api/tickets/:ticketId/messagesPosting a message authors it as the ticket's customer, matching the portal reply flow. That means the existing behavior applies automatically: replying to a resolved or closed ticket reopens it, the message is recorded, and the assigned agent is notified. An optional customerEmail lets the caller attribute the reply to a specific customer account.
Teams and escalations
GET /v1/api/teams
POST /v1/api/teams
GET /v1/api/teams/:teamId
PATCH /v1/api/teams/:teamId
POST /v1/api/teams/:teamId/members
DELETE /v1/api/teams/:teamId/members
DELETE /v1/api/teams/:teamIdTeams are fully managed, including membership. Escalation reuses the ticket endpoint and requires a target team and a reason.
Knowledge base
GET /v1/api/kb?q=&visibility=&page=&perPage=
POST /v1/api/kb
GET /v1/api/kb/:articleSlug
PATCH /v1/api/kb/:articleId
DELETE /v1/api/kb/:articleIdKnowledge base articles can be read, created, updated, and deleted. The list endpoint supports draft, published, and all visibility modes, so a product's in-app help center can stay in sync with support content.
Attachments
POST /v1/api/attachments/tickets/:ticketId
GET /v1/api/attachments/:attachmentIdAttachments use presigned S3 URLs, identical to the Desk portal. The start-upload call validates metadata, creates the attachment record, and returns a presigned PUT URL for uploading the object directly. A subsequent GET resolves a presigned download URL. The same constraints apply: a 10 MB size cap and a whitelist of MIME types.
Entitlements are enforced too. Attachment endpoints check the organization's plan and reject calls from plans that do not include the feature, with the same error code the agent API uses.
Response and Error Conventions
Every successful response uses the standard Desk envelope:
{
"message": "Ticket created.",
"data": { ... }
}Paginated lists add a meta block with total, page, and perPage.
Errors follow the same shape across the API:
{
"message": "Ticket subject is required.",
"code": "VALIDATION_ERROR",
"details": null
}The meaningful status codes are:
| Status | Code | Meaning |
|---|---|---|
| 400 | VALIDATION_ERROR | Missing or malformed fields |
| 401 | API_KEY_INVALID | Missing, unknown, or revoked key |
| 403 | FORBIDDEN | Plan restriction, for example attachments |
| 404 | NOT_FOUND | Resource does not exist in the caller's tenant |
| 409 | CONFLICT | Duplicate value, for example an email already in use |
| 429 | RATE_LIMITED | Over the per-key request limit |
Because the API delegates to the existing services, validation errors and resource errors are identical to what the agent UI would produce. Integration code can rely on the same semantics either way.
Public Documentation
The developer documentation lives at desk.monesize.com/developers and is publicly accessible. No login is required to read it.
It covers the API overview, authentication, environments, every endpoint with request and response examples, integration guidance, and error handling. The documentation is intentionally the on-ramp for any organization integrating Desk, while key management stays inside the authenticated application.
Why the Surface Is Deliberately Small
The immediate goal was not a full developer platform with SDKs, webhooks, complex OAuth flows, and dozens of integrations. The goal was a clean, secure API foundation around functionality Desk already has.
That choice has a concrete benefit. Because the API is a thin layer over existing services, there is no drift between what the API can do and what the workspace can do. Every behavior the agent UI enforces is the behavior the API enforces, because it's the same code.
The public API makes Desk usable as support infrastructure. The customer-facing experience belongs to the organization. The infrastructure and the agent workspace are Desk. And the whole thing stays free.
Comments
Post a Comment