Skip to content

API Enterprise ​

mooDesk exposes a REST API for reading and writing tickets, managing outbound webhooks and working with the knowledge base from another system. It is delivered through Moodle's own web services: there is no separate server, no separate authentication path and no separate URL scheme.

EditionEnterprise. On Starter and Pro every call is refused with featurenotavailable before any work happens.
TransportMoodle's REST server, /webservice/rest/server.php, JSON or XML
Servicemoodesk_api (shown as mooDesk API in Moodle's web services administration)
AuthenticationA Moodle web service token bound to moodesk_api, issued from mooDesk's API tokens page. Tokens from any other source, and username/password access, are refused (details).
Functions21 — 6 for tickets, 2 for merges, 5 for webhooks, 8 for the knowledge base
AuthorisationEdition → token policy → rate limit → the owner's Moodle capabilities → the token's scopes
Rate limitPer token, 300 requests per 60 seconds by default, HTTP 429 when exceeded

The settings that govern tokens and the rate limit are in Configuration → API access; the webhook deliveries the API lets you configure are described in Integrations → Webhooks; web-service prerequisites at install time are in Installation. This page does not repeat them.

Before you start ​

Four things have to be true before the first call succeeds. The first is mooDesk's; the other three are Moodle's, and mooDesk cannot switch them on for you.

WhereRequirementSymptom when missing
mooDeskThe active edition is Enterprise (License). The API access settings section and the token page's Generate form only appear on Enterprise.featurenotavailable
MoodleWeb services are enabled — Site administration → Server → Web services → Overview.accessexception — Web services are not enabled in Advanced features
MoodleThe REST protocol is enabled — … → Web services → Manage protocols.Moodle refuses the request before it reaches the plugin
MoodleThe token's owner holds webservice/rest:use at system context. Moodle core declares this capability with no role archetype, so no role has it until an administrator grants it — not even Manager.accessexception — You are not allowed to use the rest protocol (missing capability: webservice/rest:use)

The token's owner must also be a normal, active Moodle account: Moodle rejects tokens of deleted, suspended or unconfirmed users, and of users whose authentication method is No login.

The service is capability-driven, not user-list driven: there is no authorised users list to maintain in Moodle's web services administration. Whoever holds a valid token for moodesk_api can call the functions their capabilities allow, narrowed by the token's scopes.

A dedicated integration user

Because a token acts as its owner, the cleanest setup is one Moodle user per integration, in a role that carries webservice/rest:use plus exactly the mooDesk capabilities the integration needs (the capability table is in Installation → Capabilities). The token then cannot do more than that role, whatever scopes it was given.

Cron is not needed for the API itself. It is needed for the webhooks the API can create, since deliveries are drained by a scheduled task — see Integrations.

Tokens ​

Issuing a token ​

Tokens are issued from mooDesk → API tokens (/local/moodesk/api_tokens.php, in the Administration group of mooDesk's sidebar; also listed under Site administration → Plugins → Local plugins → API Tokens). Opening the page needs local/moodesk:useapi, which the Manager archetype holds by default.

To generate a token, give it a label and tick at least one scope. The page refuses to issue a token with no scopes, because such a token would be unable to call anything.

FactValue
Where the secret livesMoodle's own external_tokens table. mooDesk keeps only a companion record — label, scopes, owner — and never a copy of the secret.
ShownOnce, on the page that follows generation. It cannot be retrieved again; if it is lost, revoke it and issue another.
Acts asThe Moodle user it was issued for, with that user's capabilities. A token can never do more than its owner.
Lifetimeapi_token_lifetime_days from the token's creation, 365 days by default; 0 means it never expires. Set in Configuration → API access.
Live tokens per userAt most 5 for this service. A sixth is refused (api_token_limit_reached) until one is revoked.
Listed on the pageLabel, scopes, created, last used and expires, read live from Moodle's token record.
RevokeDeletes the Moodle token and the companion record in one transaction. The integration loses access immediately; there is no grace period.

Who may issue and manage tokens ​

A token acts as its owner, so minting one for somebody else is an act of impersonation, not of configuration. The authorities are separate:

ActionRequires
Create, list and revoke your own tokenslocal/moodesk:useapi
Do the same for another user's tokenslocal/moodesk:useapi and moodle/webservice:managealltokens
Issue a token that acts as a site administratorBeing a site administrator

moodle/webservice:managealltokens is a core Moodle capability that ships assigned to no role at all. Without it the page is strictly self-service: the User ID field is not shown, the list contains only the viewer's own tokens, and revoking a token that belongs to somebody else answers api_token_not_found — the same as a token that does not exist — so the page cannot be used to discover who holds one.

Upgrading from 2.21.0 or earlier

Before 2.23.0, any holder of local/moodesk:useapi could issue a token bound to any account and could list and revoke every token on the site. The upgrade leaves existing tokens untouched. Review them once: sign in as a site administrator, open API tokens and revoke any token whose owner and purpose you cannot account for. See Installation → Upgrading.

Scopes ​

A token carries one or more of seven scopes. A scope only ever narrows a token; it never grants anything the owner's capabilities do not already allow.

ScopeFunctions
tickets:readlist_tickets, get_ticket
tickets:writecreate_ticket, reply_ticket, transition_status, assign_ticket, merge_ticket, revert_merge
webhooks:readlist_webhooks, get_webhook
webhooks:writecreate_webhook, update_webhook, delete_webhook
kb:readlist_kb_articles, get_kb_article
kb:writecreate_kb_article, update_kb_article, transition_kb_article, delete_kb_article, add_kb_translation
kb:analyticsget_kb_analytics — deliberately not part of kb:read, so a token that syncs content cannot read behaviour metrics

A call from a token that lacks the function's scope is refused with api_scope_denied, after the capability check has already passed.

Tokens not issued by mooDesk ​

Since 2.34.0, the API only accepts tokens issued from mooDesk's API tokens page. Every other web service call is refused with api_token_unmanaged before the function does anything:

  • A token created in Moodle's Site administration → Server → Web services → Manage tokens, even one bound to the moodesk_api service. Such a token has no mooDesk record and no scopes, so mooDesk does not accept it.
  • A call through Moodle's username/password entry point, /webservice/rest/simpleserver.php.

mooDesk's own pages are not affected: they call the server with the signed-in user's browser session, not with a web service token.

Moodle's token administration is still useful to review or delete tokens (the API tokens page links to it as External services admin), but not to give an integration access. To move an integration that used one of these methods:

  1. Open mooDesk → API tokens and issue a token for the same user the integration uses now, with the scopes its calls need. See Issuing a token.
  2. Configure the integration with the new token, and check that its calls succeed.
  3. Revoke the old token in Moodle's Manage tokens screen.

The same steps are in the upgrade notes of 2.34.0 in the changelog.

Making a request ​

Every call is an HTTP request to Moodle's REST server with three fixed parameters and the function's own parameters as form fields. POST is recommended; Moodle's REST server also accepts the same parameters on a GET query string.

bash
curl -X POST "https://moodle.example.com/webservice/rest/server.php" \
  -d "wstoken=YOUR_TOKEN" \
  -d "wsfunction=local_moodesk_list_tickets" \
  -d "moodlewsrestformat=json" \
  -d "limit=10" \
  -d "offset=0"
ParameterValue
wstokenThe token, exactly as shown once at generation
wsfunctionThe function name, always prefixed local_moodesk_
moodlewsrestformatjson or xml. Moodle defaults to XML when the parameter is absent; send json explicitly.

Function parameters follow Moodle's REST conventions: scalars as plain fields, lists as indexed fields (events[0]=ticket.created&events[1]=ticket.replied), optional parameters simply omitted. Parameter names are case-sensitive.

A successful call answers HTTP 200 with the function's return value as the body:

json
[
  {
    "id": 12,
    "subject": "Cannot log in",
    "status": 1,
    "priority": 1,
    "categoryid": 3,
    "departmentid": null,
    "requesterid": 42,
    "assigneeid": null,
    "timecreated": 1780000000,
    "timemodified": 1780000000
  }
]

Timestamps are Unix seconds. IDs are Moodle's own: requesterid, assigneeid and authorid are Moodle user IDs; categoryid and departmentid are the IDs shown in mooDesk's category and department administration.

How a call is authorised ​

Every function runs the same five checks, server-side, in this order. The first failure ends the call.

#GateRefused with
1Edition — the site is Enterprise (api_access)featurenotavailable
2Token policy — the call carries a token issued from mooDesk's API tokens page (details)api_token_unmanaged
3Rate limit — the token is within its request budgetapi_rate_limited, HTTP 429
4Capability — the token's owner holds the function's capability (table per function below). Some functions accept either of two capabilities.nopermissions (a required_capability_exception)
5Scope — the token carries the function's scopeapi_scope_denied

The order is deliberate. A non-Enterprise site has no API to protect, so it learns nothing about capabilities; a call without a mooDesk token is refused before it is counted or does any work; an over-budget caller is refused before the server does any work on its behalf; and the capability check runs before the scope check because scopes only narrow — capabilities remain authoritative.

The capability is the same one the web interface checks for the equivalent action, and the service layer applies the same row-level rules afterwards: a requester's token sees only that requester's tickets, a knowledge base reader's token sees only what that reader could open in the browser. A token whose owner holds local/moodesk:viewall sees every ticket on the site; the department views of the web interface do not narrow the API.

Rate limiting ​

Requests are counted per token — not per site, which would let one noisy integration throttle every other, and not per IP, which is both too coarse and trivial to sidestep.

DefaultSetting
Requests per window300api_rate_limit — 0 disables limiting
Window length60 secondsapi_rate_window

Both are in Configuration → API access. A site that has never saved the settings page is on the defaults, not unlimited.

The algorithm is a fixed window on absolute boundaries: the clock is divided into windows of the configured length and every token gets a fresh allowance when a window ends. A client can therefore spend a full allowance at the end of one window and another at the start of the next; sustained throughput is still bounded by the limit.

Every response to a token-authenticated call carries the current state:

HeaderMeaning
RateLimit-LimitRequests allowed per window
RateLimit-RemainingRequests left in the current window
RateLimit-ResetSeconds until the current window ends

A request over the budget is refused:

http
HTTP/1.1 429 Too Many Requests
Retry-After: 37
RateLimit-Limit: 300
RateLimit-Remaining: 0
RateLimit-Reset: 37

with the usual error envelope in the body, errorcode api_rate_limited. Retry-After is the distance to the end of the current window, not a whole window.

Several web nodes

Counters live in Moodle's application cache (MUC). On a site with more than one web server they are shared only if the application cache is on a shared store such as Redis; with the default file store each node counts on its own, and the effective limit is the configured limit multiplied by the number of nodes.

Errors ​

Errors use Moodle's standard REST envelope, and — with the single exception of the rate limit — Moodle answers them with HTTP 200. Check the body, not the status code:

json
{
  "exception": "moodle_exception",
  "errorcode": "api_scope_denied",
  "message": "This API token does not have the required scope: tickets:write."
}

debuginfo is added only when Moodle debugging is on. Codes you will meet, by origin:

CodeOriginMeaning
invalidtokenMoodleUnknown token
accessexceptionMoodleWeb services disabled, token expired, IP-restricted, or the owner lacks webservice/rest:use — the message says which
invalidparameterMoodleA parameter is missing, has the wrong type, or is out of range
featurenotavailablemooDeskThe site is not Enterprise
api_token_unmanagedmooDeskThe call does not use a token issued from mooDesk's API tokens page — see Tokens not issued by mooDesk
api_rate_limitedmooDeskOver the token's budget — the one error sent as HTTP 429
nopermissionsMoodle / mooDeskThe owner lacks the function's capability, or may not see the row (required_capability_exception)
api_scope_deniedmooDeskThe token lacks the function's scope
ticketnotfound, webhook_not_found, kb_article_notfound, merge_audit_not_foundmooDeskUnknown ID
invalidstatustransition, invalidassignee, assigneenotindepartment, invalidpriority, nocategoryavailable, ticket_locked_by_mergemooDeskTicket rules, listed with the function below
merge_*, revert_merge_*mooDeskMerge rules, listed under Merges
invalidurl, webhook_events_required, webhook_invalid_event, webhook_secret_too_short, webhook_secret_too_longmooDeskWebhook validation
kb_*mooDeskKnowledge base rules, listed under Knowledge base

Reference ​

All functions are called as local_moodesk_<name>. Capability is what the token's owner must hold; where two are listed, either is enough. Every function additionally passes the edition gate, the token policy and the rate limit.

Tickets ​

FunctionScopeCapabilityDoes
list_ticketstickets:readviewall or viewownLists tickets the owner may see, newest first
get_tickettickets:readviewall or viewownOne ticket with its reply thread
create_tickettickets:writesubmitOpens a ticket as the token's owner
reply_tickettickets:writereply or viewownAdds a public reply
transition_statustickets:writemanageChanges the status
assign_tickettickets:writemanageAssigns or unassigns

Ticket summary — returned by list_tickets (as a list), create_ticket, transition_status and assign_ticket, and as ticket inside get_ticket:

FieldTypeNotes
idint
subjectstring
statusint0 new · 1 open · 2 pending · 3 on hold (legacy; no new ticket receives it) · 4 resolved · 5 closed · 6 in progress
priorityint0 low · 1 normal · 2 high · 3 urgent
categoryidint or null
departmentidint or null
requesteridintThe requester's Moodle user ID
assigneeidint or null
timecreated, timemodifiedint

The summary is deliberately small. Custom fields, the team, the course, SLA state, CSAT and the merge status are not in it — see Not in the API.

list_tickets ​

ParameterTypeDefaultNotes
limitint25Clamped to 1–100
offsetint00-based row offset; a negative value reads as 0

Returns a list of ticket summaries, newest first. An owner with viewall gets every ticket on the site; one with viewown only gets their own. There are no filters — status, category, assignee and free text cannot be narrowed server-side. Walk pages with offset += limit; an offset past the end returns an empty list.

Before 2.30.0

limit and offset were declared but not honoured: the offset was rounded down to a page of 30 rows and the whole page came back. Since 2.30.0 the two are a plain row window.

get_ticket ​

ParameterTypeNotes
ticketidint

Returns { ticket, replies[] }. Each reply is:

FieldTypeNotes
idint
authoridintMoodle user ID
messagestringHTML as stored
typeint0 public reply · 1 internal note · 2 system event
timecreatedint

Which replies come back depends on the owner, not on the token. An owner with viewall receives the whole thread, internal notes included; a requester receives public replies and system events on their own tickets only. A requester asking for somebody else's ticket is refused with nopermissions. Replies that live on tickets merged into this one are not included — the web interface shows them, the API does not.

Errors: ticketnotfound, nopermissions.

create_ticket ​

ParameterTypeDefaultNotes
subjectstring—Required, trimmed to 255 characters
descriptionstring—Required; HTML allowed
priorityint1Must be one of 0–3
categoryidint00 or an unknown/inactive category = the configured default category
departmentidint00 or an unknown/inactive department = no department

Returns the new ticket's summary. The requester is always the token's owner; there is no parameter to open a ticket on behalf of another user. The ticket starts in status open with no assignee, and the description becomes its first message. Automations, notifications and webhooks fire exactly as for a ticket created in the browser.

Errors: missingparam (empty subject or description), invalidpriority, nocategoryavailable (no usable category, including no default — see Configuration → Categories).

reply_ticket ​

ParameterTypeNotes
ticketidint
messagestringRequired; HTML allowed

Returns { replyid, ticketid, authorid }. The reply is always public — internal notes cannot be written through the API. An owner with reply may answer any ticket they can see; one with only viewown may answer their own. Replying to a ticket that was merged away is refused.

Errors: ticketnotfound, nopermissions, ticket_locked_by_merge.

transition_status ​

ParameterTypeNotes
ticketidint
statusintTarget status code

Returns the updated summary. The same state machine as the web interface applies; a step it does not allow is invalidstatustransition. Sending the current status is a no-op that still answers the summary.

Errors: ticketnotfound, invalidstatustransition, ticket_locked_by_merge.

assign_ticket ​

ParameterTypeDefaultNotes
ticketidint—
assigneeidint0An agent's Moodle user ID; 0 unassigns

Returns the updated summary. The assignee must be a mooDesk agent and, when the ticket belongs to a department, a member of it.

Errors: ticketnotfound, invalidassignee, assigneenotindepartment, ticket_locked_by_merge.

Merges ​

FunctionScopeCapabilityDoes
merge_tickettickets:writemergeticketsMerges a duplicate into another ticket
revert_mergetickets:writerevertmergeUndoes a merge within its window

The two capabilities are separate on purpose: undoing another agent's merge is a higher bar than performing one. What a merge does to the two tickets is described in Features → Merging.

merge_ticket ​

ParameterTypeDefaultNotes
sourceidint—The duplicate; it closes and becomes read-only
targetidint—The ticket that survives
reasonstring""Stored on the audit record
confirmcrossrequesterboolfalseRequired as 1 when the two tickets were raised by different people

Returns the merge record below. The API clears exactly the bars the confirmation screen clears: Enterprise, the capability, visibility on both tickets, every pre-merge constraint, and the cross-requester acknowledgement — merging across requesters makes each requester's replies readable to whoever can see the other ticket, so it must be an explicit act.

Errors: merge_same_ticket, merge_already_merged (the source is already merged), merge_target_is_merged, merge_target_is_closed, merge_source_is_newer, merge_cross_requester_required, merge_permission_insufficient, merge_target_invalid.

revert_merge ​

ParameterTypeNotes
auditidintFrom the merge record

Returns the same merge record, now with revertedat set. A merge can be undone for 30 days; after that, or once undone, the call is refused whatever a previous response's revertable said.

Errors: merge_audit_not_found, revert_merge_already_reverted, revert_merge_ttl_expired.

Merge record — returned by both functions:

FieldTypeNotes
auditidintWhat revert_merge takes
sourceid, targetidint
actoridintThe agent who merged
mergedatint
reasonstringEmpty when none was given
revertedatint or nullnull while the merge stands
revertableboolWhether it can be undone right now
replies, internalnotes, attachments, historyentriesintWhat the source carried and the target now reads across the link — nothing is copied
conflictsstring[]Conflict keys the operator was shown before confirming, e.g. cross_requester

Webhooks ​

FunctionScopeCapabilityDoes
list_webhookswebhooks:readmanagewebhooksLists every webhook
get_webhookwebhooks:readmanagewebhooksOne webhook
create_webhookwebhooks:writemanagewebhooksCreates a subscription
update_webhookwebhooks:writemanagewebhooksReplaces a subscription's fields
delete_webhookwebhooks:writemanagewebhooksDeletes a subscription

These manage the subscriptions. What a delivery looks like, how it is signed, the retry schedule and the delivery log are in Integrations → Webhooks; the event names are the same six listed there.

Webhook summary — returned by every function except delete_webhook. The signing secret is never returned, with one exception: the create_webhook response (below).

FieldTypeNotes
idint
namestring
urlstring
eventsstring[]e.g. ["ticket.created", "ticket.replied"]
activeint1 active, 0 paused
timecreated, timemodifiedint

list_webhooks ​

No parameters. Returns a list of summaries.

get_webhook, delete_webhook ​

ParameterType
idint

get_webhook returns the summary; delete_webhook returns { "success": true }. Error: webhook_not_found.

create_webhook, update_webhook ​

ParameterTypeDefaultNotes
idint—update_webhook only
namestring—Required
urlstring—Required; must be a valid URL. Use HTTPS — see Integrations for what the endpoint must accept
eventsstring[]—At least one of the six event types
secretstring""Write-only. At least 32 characters (at most 255) when supplied
activeint11 active, 0 paused

create_webhook — leave secret empty or omit it and mooDesk generates one. The response is the summary plus secret, the stored secret, supplied or generated. This is the only time the API returns it: store it on the receiving end straight away.

update_webhook — returns the summary, and is otherwise a full replacement: send name, url, events and active. An empty or omitted secret keeps the current one; a new value replaces it. A secret cannot be removed. To replace a secret with a generated one, use Rotate secret on the webhook's edit page in mooDesk.

Changed in 2.33.0

Before 2.33.0, create_webhook accepted a secret of any length or none at all, and an empty secret on update_webhook cleared it. A caller that relied on that must now either send a secret of at least 32 characters or read the generated one from the create_webhook response.

Errors: missingparam (empty name), invalidurl, webhook_events_required, webhook_invalid_event, webhook_secret_too_short, webhook_secret_too_long, webhook_not_found.

Knowledge base ​

FunctionScopeCapabilityDoes
list_kb_articleskb:readviewkbLists the article rows the owner may see
get_kb_articlekb:readviewkbOne row, with its body
create_kb_articlekb:writeeditkb or managekbCreates a draft
update_kb_articlekb:writeeditkb or managekbChanges title, body, category, audience or language
transition_kb_articlekb:writemanagekbPublishes, unpublishes, archives or restores
delete_kb_articlekb:writemanagekbDeletes one row
add_kb_translationkb:writeeditkb or managekbAdds a translation as a new draft row Pro feature
get_kb_analyticskb:analyticsmanagekb or viewreportsThe analytics report over a date range Pro feature

The knowledge base itself, its statuses, audiences and translations are described in Features → Knowledge base. Two things to hold on to when reading the reference:

  • id is always a translation row, never an article group. An article in three languages is three rows sharing one groupid. get_kb_article returns exactly the row asked for and never swaps in a sibling; a row the owner may not see is refused, not substituted.
  • Visibility follows the owner, through the same policy as the pages. A requester's token sees published rows for everyone; an agent's also sees Agents only rows; an editor's or manager's sees drafts and archived rows too. The token's scopes cannot widen that.

Although the API is Enterprise, the two functions marked Pro feature sit on knowledge base features that are themselves Pro (kb_multilingual, kb_analytics); an Enterprise site has both.

Article — returned by get_kb_article, create_kb_article, update_kb_article, transition_kb_article and add_kb_translation. list_kb_articles returns the same shape with excerpt (first 200 characters as plain text) instead of body:

FieldTypeNotes
idintTranslation row ID
groupidintThe group's canonical row ID
langstringLanguage code of this row
titlestring
statusint0 draft · 1 published · 2 archived
audienceint0 everyone · 1 agents only
categoryidint or null
timecreated, timemodifiedint
timepublishedint or nullFirst publication
translationslist of { id, lang, status }The rows of the group the owner may see, this one included
bodystringHTML as stored — get and the write functions
excerptstringlist only

Authorship (createdby, usermodified) is not exposed.

list_kb_articles ​

All parameters optional.

ParameterTypeDefaultNotes
statusint—Honoured for editors and managers only; ignored for everyone else, whose visibility the policy decides
categoryidint—0 = uncategorised
langstring—Exact row language
qstring—Free-text search, best match first
resolvestring—A language code: return one row per group, chosen for that language
limitint25Clamped to 1–100
offsetint0

Without resolve, every visible row comes back, one line per translation. With resolve=es, each group contributes the row the web interface would show to a reader whose language is es — that language, its Moodle parent language, the site language, then the canonical row. The session language of the token's owner is never consulted, so the answer does not change with a profile setting.

get_kb_article, delete_kb_article ​

ParameterType
idint

get_kb_article returns the article with its body; delete_kb_article returns { "success": true }. Deleting a row also removes its files, feedback votes, analytics and search entry; the group survives on its other rows. Errors: kb_article_notfound, nopermissions.

create_kb_article ​

ParameterTypeDefaultNotes
titlestring—Required
bodystring""HTML
categoryidint00 = none
audienceint00 everyone, 1 agents only
langstring""The site language when empty or not an installed language pack

Returns the new row, always a draft. Images referenced in body must already be addressable by URL — there is no file upload through the API.

Errors: kb_title_required, kb_invalid_category, kb_invalid_audience.

update_kb_article ​

ParameterTypeNotes
idintRequired
title, body, categoryid, audience, langas aboveEach optional

Only the parameters sent are changed; the rest are left alone. Send categoryid=0 to clear the category. Status is not a field here — use transition_kb_article. Category and audience belong to the group and are written to every translation row; changing the audience of a published article needs managekb, as in the web interface. lang is a Pro feature.

Errors: kb_article_notfound, kb_title_required, kb_invalid_category, kb_invalid_audience, kb_invalid_lang, nopermissions.

transition_kb_article ​

ParameterTypeNotes
idint
actionstringpublish, unpublish, archive or restore

Returns the row after the change. The rules are the knowledge base's own: a step the status machine does not allow is kb_invalid_transition, and a row with an empty body cannot be published (kb_body_required). Any other action is invalidparameter.

add_kb_translation ​

ParameterTypeDefaultNotes
sourceidint—Any row of the group being translated
langstring—An installed language the group does not have yet
titlestring—Required
bodystring""HTML

Returns the new row, a draft in the same group. Category and audience are the group's; nothing else is copied from the source.

Errors: kb_article_notfound, kb_invalid_lang (not an installed language pack), kb_translation_exists, kb_title_required.

get_kb_analytics ​

All parameters optional.

ParameterTypeDefaultNotes
fromint—Unix timestamp; the day it falls on. Defaults to 30 days before to
toint—Unix timestamp; the day it falls on. Defaults to today; cannot be in the future
langstring—Language filter
categoryidint00 = all
bystringrowrow (per translation) or group (per article)

The range is clamped to 90 days ending at to, the same ceiling as the analytics page. Returns the effective from and to, by, a summary and the five tables of the page:

KeyContents
summaryviews, votes, helpful, helpfulrate, searches, zeroresults, viewsbyday[] (day at midnight, views)
mostviewed[], lowesthelpful[]Per article: articleid (by row) or groupid (by group; the other is null), title, lang, categoryid, translations (row count), views, votes, helpful, helpfulrate
zeroresults[]querynorm (as stored — already redacted), redacted, count, lastseen
deflection[]Same article fields plus impressions, opens, openrate
bylanguage[]lang, published, views, votes, helpful, helpfulrate

Rates are floats in [0, 1], or null when there was nothing to divide.

Functions that are not part of the API ​

Four more external functions exist in mooDesk, registered for the plugin's own pages (ajax => true) and not included in the moodesk_api service:

FunctionUsed by
local_moodesk_search_active_ticketsThe merge target autocomplete
local_moodesk_search_kb_articlesArticle suggestions on the New Ticket form
local_moodesk_search_kb_for_replyThe Insert article tool of the ticket composer
local_moodesk_set_kb_feedbackWas this article helpful? on an article page

They are called with a browser session, not a token, and are gated on their own features rather than on api_access. A token bound to moodesk_api cannot call them, and their parameters and return values are not a public contract.

Not in the API ​

What an integration might look for and will not find in 2.38.1. None of these has a workaround inside the API.

WantedStatus
Filtering list_tickets by status, category, assignee, department or textNot available — page through and filter client-side
Ticket fields beyond the summary: custom fields, team, course, SLA state, CSAT, merge status, the requester's nameNot returned
Changing a ticket's priority, category, department or teamNot available — only status and assignee can be changed
Opening a ticket on behalf of another userNot available — the requester is always the token's owner
Internal notes, attachments (reading or adding)Not available — replies are public and text-only
Replies of tickets merged into the requested one in get_ticketNot included
A ticket's history, the delivery log of a webhookNot available
Categories, departments, agents, teams, custom fields, automations, CSAT — listing or managingNot available
File upload for knowledge base imagesNot available — reference images by URL
Recording Was this article helpful? votesNot available by design: no integration votes on a person's behalf
Bulk operations, ETags / conditional requests, webhooks about API activityNot available
IP restriction on a tokenNot available — mooDesk issues tokens without one, and Moodle's token administration cannot edit a token once it exists
OAuth, per-token expiry overrides, more than 5 live tokens per userNot available

Verified against mooDesk 2.38.1.

mooDesk is a product by digitalMood.
mooDesk is an independent third-party plugin and is not affiliated with or endorsed by Moodle HQ.
Moodle® is a registered trademark of Moodle Pty Ltd.

mooDesk is a product by digitalMood.
mooDesk is an independent third-party plugin and is not affiliated with or endorsed by Moodle HQ.
Moodle® is a registered trademark of Moodle Pty Ltd.