Members API
This section of the DeskOS API covers members, companies and membership status. From an outside system such as a WiFi controller, an access-control platform, an HR or CRM tool, n8n or Zapier, or an AI agent you can list every member and company in a workspace, read whether a member's membership is live right now, keep your own copy in sync as things change, create and update members and companies, and write your own values back onto a member through custom fields.
The typical use is a vendor integration. A WiFi provider lists the members at a campus, checks who currently holds a live subscription, generates a PIN for each of them, and writes that PIN back onto the member as a custom field so the operator's staff can see it on the member's profile in DeskOS.
Getting a key, sending it, campus rules, the response shape and the errors every section shares are all on the Authentication page. This page only covers what is specific to members.
Read the general agent instructions on the Authentication page, then the member-specific rules below.
Base path
All paths on this page are relative to:
https://oscar.nova.deskos.net/api/v1/accounts/external/
Your Developer Settings page shows the exact base URL for your workspace.
Permissions
Member permissions all start with members:. An admin ticks them per key on the Developer Settings page. A key only gets member permissions if the admin creating it holds Members: manage themselves.
| Permission | What it allows | Endpoints |
|---|---|---|
members:members:read | Read members | GET members/, GET members/<id>/ |
members:members:write | Create and update members, deactivate and reactivate them | POST members/, PATCH members/<id>/, POST members/<id>/deactivate/, POST members/<id>/reactivate/ |
members:companies:read | Read companies | GET companies/, GET companies/<id>/ |
members:companies:write | Create and update companies | POST companies/, PATCH companies/<id>/ |
members:memberships:read | Read subscriptions and one member's live status | GET memberships/, GET members/<id>/membership/ |
members:custom_fields:read | Read the custom field definitions | GET custom-fields/ |
members:custom_fields:write | Write custom field values | PATCH members/<id>/custom-fields/, PATCH companies/<id>/custom-fields/ |
members:campuses:read | Read the campus list | GET campuses/ |
A typical vendor integration ("provision our service for whoever is a live member") needs members:members:read, members:memberships:read, members:custom_fields:read and members:custom_fields:write. It does not need either write permission on members or companies.
A directory sync ("push our HR joiners into DeskOS") needs members:members:read, members:members:write and members:companies:read.
GET health/ needs no permission and works with any key. Use it to confirm the key carries the members: permissions you expect before doing anything else.
There is no delete endpoint for a member or a company, and there never will be. POST members/<id>/deactivate/ is the strongest thing an outside system can do: the member can no longer sign in, and nothing is erased. A bug in an integration cannot destroy an operator's records.
Campuses
Members, companies and custom field definitions all belong to a campus. The general campus rules are on the Authentication page. For this section:
- List endpoints accept
?campus_id=to narrow results. Without it they return everything the key can reach. - A member is visible to a campus-restricted key if the member belongs to one of the key's campuses or their company is homed at one. A member who switched campus is never lost to the vendor that provisions them.
- Create resolves the campus in this order:
campus_idin the body, else the company's home campus (for a member), else the key's default campus, else the organisation's only campus. If none of those applies you get a 400 asking forcampus_id. - Custom field definitions are per campus. A field named
wifi_pinat Acme North is a different definition fromwifi_pinat Acme South. FetchGET custom-fields/for the campus you are about to write to.
Membership status
Every member and company payload carries a status and a membership object. Status is computed on every read, never stored. It answers one question: does this person hold a live subscription at this campus right now?
| Status | Meaning |
|---|---|
active | The member's company holds a live subscription at the campus |
on_notice | The subscription is live but running out its notice period, so it ends soon |
pending | The account exists but is still waiting for the operator to approve it |
inactive | No live subscription, or the account has been deactivated |
banned | The operator has banned this member |
on_notice still means the member may use the space today. Treat active and on_notice together as "entitled", and pending, inactive and banned as "not entitled".
The membership object says why, and which subscription the answer came from:
{
"status": "active",
"reason": "entitled",
"campus_id": 7,
"start": "2026-09-01T00:00:00+05:30",
"end": "2026-12-31T23:59:59+05:30",
"subscription_id": "8f0b2a0e-3c1d-4e2b-9a77-2c1f5b7d4a10",
"kind": "time_based"
}
Reason codes, in the order they are checked:
| Reason | Meaning |
|---|---|
banned | The member is banned |
deactivated | The member account has been deactivated |
pending_approval | The member is waiting for approval |
no_company | The member is not attached to a company |
no_campus | There is no campus to evaluate the member at |
no_active_subscription | The company has no live subscription at this campus |
on_notice | Live, but serving its notice period |
entitled | The company holds a live subscription at this campus |
kind is time_based (a desk or cabin subscription) or visit_based (a day-pass or visit pack). When a company holds several live subscriptions, the one whose window runs longest is the one reported, so end covers the member's whole entitlement.
A subscription sold at one campus does not entitle a member at another. GET members/<id>/membership/?campus_id= evaluates at whichever campus you ask about; without campus_id it uses the member's current campus.
API endpoints
| Method | Path | Permission | Purpose |
|---|---|---|---|
| GET | health/ | none | What this key is and can do |
| GET | campuses/ | members:campuses:read | Locations this key can reach |
| GET | custom-fields/ | members:custom_fields:read | Field definitions for members and companies |
| GET | members/ | members:members:read | List and filter members |
| POST | members/ | members:members:write | Create a member on a company |
| GET | members/<id>/ | members:members:read | One member |
| PATCH | members/<id>/ | members:members:write | Update a member |
| POST | members/<id>/deactivate/ | members:members:write | Stop a member signing in |
| POST | members/<id>/reactivate/ | members:members:write | Undo a deactivation |
| GET | members/<id>/membership/ | members:memberships:read | Computed status at a campus |
| PATCH | members/<id>/custom-fields/ | members:custom_fields:write | Write custom field values |
| GET | companies/ | members:companies:read | List and filter companies |
| POST | companies/ | members:companies:write | Create a company |
| GET | companies/<id>/ | members:companies:read | One company |
| PATCH | companies/<id>/ | members:companies:write | Update a company |
| PATCH | companies/<id>/custom-fields/ | members:custom_fields:write | Write custom field values |
| GET | memberships/ | members:memberships:read | List subscriptions |
Member ids are UUIDs. Company ids are integers. Both are returned on every payload.
1. List campuses
GET /api/v1/accounts/external/campuses/
{
"success": true,
"count": 2,
"campuses": [
{
"id": 7,
"name": "Acme North",
"city": "Bengaluru",
"address": "12 MG Road",
"state": "Karnataka",
"country": "IN",
"timezone": "Asia/Kolkata",
"currency": "INR"
}
]
}
2. List custom field definitions
GET /api/v1/accounts/external/custom-fields/
GET /api/v1/accounts/external/custom-fields/?campus_id=7
Returns the active definitions for both members and companies. The name is the slug you use when writing a value.
{
"success": true,
"members": [
{
"id": 12,
"campus_id": 7,
"label": "WiFi PIN",
"name": "wifi_pin",
"field_type": "text",
"options": [],
"has_expiry": false,
"is_required": false,
"ask_on_create": false,
"show_to_user": true
}
],
"companies": []
}
field_type is one of text, boolean, date, number, select, url. A select field lists its permitted values in options. A boolean is written as true or false. A date is YYYY-MM-DD. A url must start with http:// or https://.
If the field you want does not exist yet, an admin creates it in the DeskOS console: Memberships, then the gear icon on the Members or Companies tab. Turn on Show to member if the value should appear on the member's own dashboard.
3. List members
GET /api/v1/accounts/external/members/
GET /api/v1/accounts/external/members/?campus_id=7&status=active,on_notice
GET /api/v1/accounts/external/members/?updated_since=2026-09-13T06:00:00Z
| Filter | Notes |
|---|---|
campus_id | Members at this campus, or whose company is homed there |
company_id | Members of one company |
status | Comma-separated: active, on_notice, pending, inactive, banned |
email | Exact match, case-insensitive |
updated_since | ISO 8601 datetime. Everything changed at or after this moment |
page, page_size | page_size defaults to 50, maximum 200 |
{
"success": true,
"count": 143,
"page": 1,
"page_size": 50,
"has_next": true,
"members": [
{
"id": "3f2b6c14-77a1-4d6e-8a19-9c0e7b5a1234",
"email": "ada@widgets.example",
"first_name": "Ada",
"last_name": "Lovelace",
"display_name": "Ada Lovelace",
"mobile": "9876500011",
"company": {"id": 41, "name": "Widgets Ltd"},
"company_role": "admin",
"campus_ids": [7],
"home_campus_id": 7,
"status": "active",
"membership": {
"status": "active",
"reason": "entitled",
"campus_id": 7,
"start": "2026-09-01T00:00:00+05:30",
"end": "2026-12-31T23:59:59+05:30",
"subscription_id": "8f0b2a0e-3c1d-4e2b-9a77-2c1f5b7d4a10",
"kind": "time_based"
},
"custom_fields": {
"wifi_pin": {"value": "4821", "expires_at": null}
},
"is_banned": false,
"is_active": true,
"date_joined": "2026-04-02T11:04:22+05:30",
"updated_at": "2026-09-13T09:15:00+05:30"
}
]
}
company_role is admin (a company's own administrator, who can manage their colleagues in the member portal) or member.
Store the highest updated_at you have seen, then poll ?updated_since= with that value. Editing a member, moving them between companies, deactivating them, and writing a custom field value all move updated_at, so a poller sees every change. A subscription starting or ending does not move it, because status is computed rather than stored, so re-check status on the members you care about (or poll GET memberships/?updated_since=) rather than relying on updated_at alone.
4. Create a member
POST /api/v1/accounts/external/members/
{
"first_name": "Grace",
"last_name": "Hopper",
"email": "grace@widgets.example",
"mobile": "9876500022",
"company_id": 41,
"company_role": "member",
"custom_fields": {"wifi_pin": "7734"}
}
first_name, last_name, email and company_id are required. The member is created on the company's home campus unless you send campus_id.
Email and mobile must be unique within the organisation. The same email may exist in a different DeskOS workspace. A member created this way is approved immediately and appears in the admin console exactly as if a staff member had added them, including the entry in the company's activity log, which records the name of the key that did it.
Any custom field an admin has marked Required and Ask when creating must carry a value, or the create is refused and nothing is written:
{
"success": false,
"errors": {"custom_fields": {"wifi_pin": "This field is required."}}
}
Answers 201 with the full member object under member.
5. Update a member
PATCH /api/v1/accounts/external/members/<id>/
Send only the keys you want to change: first_name, last_name, email, mobile, company_role, company_id, custom_fields.
{"mobile": "9876500099", "company_id": 52}
Moving a member to another company adds that company's home campus to the member and writes the move to both companies' activity logs. Send "mobile": "" to clear a mobile number.
6. Deactivate and reactivate
POST /api/v1/accounts/external/members/<id>/deactivate/
POST /api/v1/accounts/external/members/<id>/reactivate/
No body. Both are idempotent and report whether anything actually changed:
{"success": true, "changed": true, "member": {"...": "..."}}
A deactivated member cannot sign in and reads as status: "inactive" with reason: "deactivated". Their record, history and invoices are untouched.
7. Read one member's membership
GET /api/v1/accounts/external/members/<id>/membership/
GET /api/v1/accounts/external/members/<id>/membership/?campus_id=9
The cheapest way to answer "may this person use the space right now". Returns the same membership object as the member payload, plus a human-readable label:
{
"success": true,
"member_id": "3f2b6c14-77a1-4d6e-8a19-9c0e7b5a1234",
"status": "active",
"membership": {
"status": "active",
"reason": "entitled",
"campus_id": 7,
"start": "2026-09-01T00:00:00+05:30",
"end": "2026-12-31T23:59:59+05:30",
"subscription_id": "8f0b2a0e-3c1d-4e2b-9a77-2c1f5b7d4a10",
"kind": "time_based",
"label": "Company holds a live subscription at this campus"
}
}
8. Write custom field values onto a member
PATCH /api/v1/accounts/external/members/<id>/custom-fields/
The short form, keyed by the field name:
{"wifi_pin": "4821"}
The long form, when you need an expiry date or want to name the campus explicitly:
{
"campus_id": 7,
"custom_fields": [
{"field_id": 12, "value": "4821", "expires_at": "2027-03-31"}
]
}
Values are validated against the definitions on the member's current campus unless campus_id says otherwise. Writing the same field twice updates the value rather than adding a second one. A field an admin marked Required cannot be cleared to an empty value. An unknown field name is a 400, so a typo never silently does nothing.
expires_at is only stored on a definition that has expiry turned on. When it is, DeskOS reminds the operator's staff before the value lapses, which is how "this member's WiFi PIN expires at the end of the quarter" gets surfaced without your system having to chase it.
9. Companies
GET /api/v1/accounts/external/companies/?campus_id=7&status=active&name=widgets
POST /api/v1/accounts/external/companies/
GET /api/v1/accounts/external/companies/<id>/
PATCH /api/v1/accounts/external/companies/<id>/
PATCH /api/v1/accounts/external/companies/<id>/custom-fields/
{
"id": 41,
"name": "Widgets Ltd",
"slug": "widgets-ltd",
"industry": "technology",
"website": "https://widgets.example",
"address": "Level 4, 12 MG Road",
"campus_id": 7,
"member_count": 18,
"status": "active",
"is_active": true,
"membership": {"...": "..."},
"billing": {
"legal_entity_name": "Widgets Private Limited",
"gst_number": "29AABBC1234D1Z5",
"address_line_1": "Level 4, Prestige Tower",
"address_line_2": "MG Road",
"city": "Bengaluru",
"state": "Karnataka",
"pincode": "560001",
"country": "India",
"is_default": true
},
"custom_fields": {"gst_number": {"value": "29AABBC1234D1Z5", "expires_at": null}},
"added_on": "2026-01-11T10:00:00+05:30",
"updated_on": "2026-09-10T16:40:00+05:30"
}
member_count counts active members only. A company's status is the same computation as a member's, evaluated at its home campus, so it answers "is this company a paying tenant right now". is_active is the same answer as a boolean, and it is true for on_notice as well as active, because a company serving its notice period is still paying until the period ends.
billing is the company's legal identity: the legal entity name, the tax number and the billing address that goes on its invoices. A company can hold several addresses; the one flagged as default is the one returned, and where none is flagged it is the first the operator entered. It is null for a company that has never had one filled in. This is the customer's identity today. For the identity as it stood on a particular document, read billing_to on the invoice itself in the Billing API.
Creating a company needs name, plus campus_id when the organisation has more than one campus. website, industry, address and custom_fields are optional. Company list filters are campus_id, status (active, on_notice, inactive), name (contains, case-insensitive), updated_since, page and page_size.
10. List memberships
GET /api/v1/accounts/external/memberships/?campus_id=7&kind=time_based&status=active
The subscriptions themselves, rather than the computed per-member answer. Use it to build a renewals view, or to poll for subscriptions that changed.
| Filter | Notes |
|---|---|
campus_id | Subscriptions valid at this campus |
company_id | One company's subscriptions |
kind | time_based or visit_based |
status | Comma-separated raw subscription statuses: active, cancelled, expired, pending, upcoming |
updated_since | ISO 8601 datetime |
page, page_size | page_size defaults to 50, maximum 200 |
{
"success": true,
"count": 24,
"page": 1,
"page_size": 50,
"has_next": false,
"memberships": [
{
"id": "8f0b2a0e-3c1d-4e2b-9a77-2c1f5b7d4a10",
"kind": "time_based",
"status": "active",
"on_notice": false,
"company": {"id": 41, "name": "Widgets Ltd"},
"campus_id": 7,
"start": "2026-09-01T00:00:00+05:30",
"end": "2026-12-31T23:59:59+05:30",
"total_seats": 6,
"billing_cycle": "monthly",
"updated_at": "2026-09-01T09:00:00+05:30"
},
{
"id": "b2d1f77c-1a44-4f01-9d3e-7c5b2a8e0011",
"kind": "visit_based",
"status": "active",
"on_notice": false,
"company": {"id": 55, "name": "Northwind"},
"campus_id": 7,
"start": "2026-09-05T00:00:00+05:30",
"end": "2026-10-05T23:59:59+05:30",
"total_visits": 10,
"remaining_visits": 4,
"updated_at": "2026-09-12T14:20:00+05:30"
}
]
}
status here is the subscription's own status, which is not the same vocabulary as a member's computed status. A subscription with status: "active" whose window has already passed does not entitle anyone; if you want the entitlement answer, read the member's membership instead.
A vendor integration, end to end
This is the shape of a WiFi, access-control or printing integration.
Once, at onboarding. The operator creates a key in Developer Settings with members:members:read, members:memberships:read, members:custom_fields:read and members:custom_fields:write, restricted to the campuses you serve. They create a member custom field for whatever you write back (for example "WiFi PIN"), with Show to member on if the member should see it. They send you the key and their organisation slug.
Once, at first run. Call GET health/ to confirm the key and see which campuses it reaches. Call GET custom-fields/ and store the definition id and name of your field per campus. Call GET members/?status=active,on_notice and page through it to build your initial copy.
Continuously. Have the operator point a webhook at you for member.* and membership.* so joiners, leavers and edits reach you in seconds rather than at your next poll. Keep polling anyway, more slowly: GET members/?updated_since=<your high-water mark> and GET memberships/?updated_since= catch anything a delivery outage lost. Re-check status for anyone whose subscription window you know is closing.
Whenever you provision. PATCH members/<id>/custom-fields/ with {"wifi_pin": "4821"}. The operator's staff now see the value on the member's profile, and the member sees it on their dashboard if the field is shown to them.
When someone stops being entitled. Their status becomes inactive or banned. Withdraw your access. Do not deactivate the member in DeskOS: that is the operator's decision, not yours.
Code examples
curl
BASE="https://oscar.nova.deskos.net/api/v1/accounts/external"
KEY="dk_live_xxxxxxxxxxxxxxxxxxxxxxxx"
# 1. What can this key do?
curl -s "$BASE/health/" -H "Authorization: Api-Key $KEY"
# 2. Everyone entitled at campus 7
curl -s "$BASE/members/?campus_id=7&status=active,on_notice&page_size=200" \
-H "Authorization: Api-Key $KEY"
# 3. Is this one person entitled right now?
curl -s "$BASE/members/3f2b6c14-77a1-4d6e-8a19-9c0e7b5a1234/membership/" \
-H "Authorization: Api-Key $KEY"
# 4. Write our PIN back onto them
curl -X PATCH "$BASE/members/3f2b6c14-77a1-4d6e-8a19-9c0e7b5a1234/custom-fields/" \
-H "Authorization: Api-Key $KEY" \
-H "Content-Type: application/json" \
-d '{"wifi_pin": "4821"}'
Python
import os
import requests
BASE = "https://oscar.nova.deskos.net/api/v1/accounts/external"
HEADERS = {"Authorization": f"Api-Key {os.environ['DESKOS_API_KEY']}"}
def get(path, **params):
r = requests.get(f"{BASE}/{path}", params=params, headers=HEADERS, timeout=30)
data = r.json()
if not data.get("success"):
raise RuntimeError(f"DeskOS refused the request: {data.get('errors') or data.get('detail')}")
return data
def entitled_members(campus_id):
"""Every member who may use the space at this campus right now."""
page = 1
while True:
data = get("members/", campus_id=campus_id, status="active,on_notice",
page=page, page_size=200)
yield from data["members"]
if not data["has_next"]:
return
page += 1
def set_wifi_pin(member_id, pin):
r = requests.patch(f"{BASE}/members/{member_id}/custom-fields/",
json={"wifi_pin": pin}, headers=HEADERS, timeout=30)
data = r.json()
if not data.get("success"):
raise RuntimeError(data.get("errors"))
return data["member"]
for member in entitled_members(campus_id=7):
if not member["custom_fields"].get("wifi_pin"):
set_wifi_pin(member["id"], generate_pin(member["email"]))
JavaScript / Node.js
const BASE = 'https://oscar.nova.deskos.net/api/v1/accounts/external';
const headers = {
'Authorization': `Api-Key ${process.env.DESKOS_API_KEY}`,
'Content-Type': 'application/json',
};
async function changedSince(isoTimestamp) {
const url = `${BASE}/members/?updated_since=${encodeURIComponent(isoTimestamp)}&page_size=200`;
const res = await fetch(url, { headers });
const data = await res.json();
if (!data.success) throw new Error(JSON.stringify(data.errors || data.detail));
return data.members;
}
async function setCustomField(memberId, name, value) {
const res = await fetch(`${BASE}/members/${memberId}/custom-fields/`, {
method: 'PATCH',
headers,
body: JSON.stringify({ [name]: value }),
});
const data = await res.json();
if (!data.success) throw new Error(JSON.stringify(data.errors));
return data.member;
}
n8n / Zapier / Make
Use an HTTP Request node. Set the method and URL from the endpoint table, add a header Authorization with value Api-Key dk_live_..., set the body type to JSON, and map your fields. Read success from the response to branch on failure. For a scheduled sync, store the last run's timestamp and pass it as updated_since.
Instructions for AI agents
The general rules (how a key is obtained, how to handle it, what to do on 401 and 403) are on the Authentication page. Read those first. This section adds what is specific to members.
Permissions to ask for
Ask only for the members: permissions the task needs. Read permissions are cheap; write permissions are not.
- "Provision our WiFi for whoever is a live member":
members:members:read,members:memberships:read,members:custom_fields:read,members:custom_fields:write. Nomembers:members:write. - "Mirror the member directory into our dashboard":
members:members:readandmembers:companies:readonly. - "Push our HR joiners into DeskOS":
members:members:write, plus the two read permissions to check for duplicates first.
If you are asking for members:members:write, say so explicitly to the human and explain what you will create or change. It is the permission that alters an operator's records.
Recommended first steps
- Call
GET health/. It confirms the key works and returnsscopes,allowed_campusesanddefault_campus. Use the answer to decide whether you need to sendcampus_idand whether every operation you plan is in scope, before doing anything else. - Call
GET campuses/andGET custom-fields/and cache what you get. Custom field definitions are per campus; do not assume a field at one campus exists at another. - If the field you need to write does not exist, stop and ask the human to create it in Memberships, gear icon, on the relevant campus. Do not work around a missing field by writing into a different one.
- Page through lists with
page_sizeup to 200 rather than fetching members one at a time.
Behaviour rules
- Never treat
statusas stored state you can set. It is computed from subscriptions. If a member reads asinactiveand you think they should be active, the answer is in their company's subscriptions, not in anything you can write. on_noticemeans entitled. A member serving a notice period may still use the space. Do not cut off their access.- Do not deactivate members to reflect your own system's state. Deactivation is the operator's decision about their own member. Withdraw access on your side instead.
- Check for an existing member before creating one. Search
GET members/?email=first. Creating a duplicate splits a person's history in two and the operator has to merge it by hand. - A custom field belongs to whoever defined it. Write only the fields your integration owns. Do not overwrite a field the operator's staff maintain, such as an ID number or a vehicle registration, unless the human explicitly asked you to.
- Respect the daily limit.
health/showsrequests_todayagainstdaily_limit. The limit is shared by every section the key can reach. Useupdated_sincefor incremental syncs rather than re-reading the whole directory on every run. - Treat 404 on a member as "not visible to this key", not proof that they never existed. They may be at a campus the key cannot reach.
- Do not create test members. Use
health/,campuses/andcustom-fields/to verify connectivity. If you genuinely must create one, name it clearly as a test, tell the human, and deactivate it yourself afterwards.
Reporting back to the human
When you finish, tell the human:
- the key name you used (never the key itself),
- which permissions it turned out to need,
- how many members and companies you read, and exactly what you created or changed,
- which custom fields you wrote, on how many members,
- any 403 or 429 you hit, so they can adjust the key.
Frequently asked questions
Can I delete a member? No. Deactivate is the strongest write this API exposes. Deletion stays in the admin console, where a human can see what is about to be lost.
Why is a member inactive when their company clearly pays?
Status is campus-scoped. A subscription sold at one campus does not entitle a member at another. Call GET members/<id>/membership/?campus_id= at the campus you care about, and check reason: no_active_subscription means the company has nothing live there, while no_company or pending_approval are account problems rather than billing ones.
Does updated_at change when a subscription starts or ends?
No. Status is computed, so a subscription change does not touch the member row. Poll GET memberships/?updated_since= alongside GET members/?updated_since=, or re-check status for the members whose windows you know are closing.
Can I set a member's password or send them an invitation? Not through this API. A member created here exists and is approved. To sign them in to your own app, or set their password, use the Member Sign-in API. To have DeskOS send its own invitation, the operator invites them through the admin console.
Can I create the custom field itself through the API? No. Definitions are created by an admin in the console, so a human decides what appears on member profiles. The API reads definitions and writes values.
Can the same key also raise tickets, book rooms or read invoices? Yes, if the admin ticked those permissions on it as well. One key can span several sections. See Service Requests, Meeting Rooms and Billing.
Can I get the invoices for a company I found here?
Yes. GET invoices/?company_id= in the Billing API takes the same company id this section returns.
Can DeskOS push changes to me instead of me polling?
Yes. An admin can point a URL of yours at member, company and membership events on the Webhooks tab of Developer Settings, and the payload is the same shape this API returns. See Webhooks. Keep a periodic updated_since pass as a floor: a delivery that exhausts its retries is not resent on its own.
Quick reference
Base: https://oscar.nova.deskos.net/api/v1/accounts/external/
Auth: Authorization: Api-Key dk_live_...
GET health/
GET campuses/
GET custom-fields/?campus_id=
GET members/?campus_id=&company_id=&status=&email=&updated_since=&page=&page_size=
POST members/ {first_name, last_name, email, company_id, ...}
GET members/<id>/
PATCH members/<id>/ {first_name?, email?, mobile?, company_id?, ...}
POST members/<id>/deactivate/
POST members/<id>/reactivate/
GET members/<id>/membership/?campus_id=
PATCH members/<id>/custom-fields/ {"wifi_pin": "4821"}
GET companies/?campus_id=&status=&name=&updated_since=&page=&page_size=
POST companies/ {name, campus_id?, website?, industry?, address?}
GET companies/<id>/
PATCH companies/<id>/
PATCH companies/<id>/custom-fields/ {"gst_number": "..."}
GET memberships/?campus_id=&company_id=&kind=&status=&updated_since=
Company payload also carries: is_active (boolean), billing (legal name, tax number, address)
Permissions: members:members:read, members:members:write, members:companies:read,
members:companies:write, members:memberships:read,
members:custom_fields:read, members:custom_fields:write,
members:campuses:read
Statuses: active, on_notice, pending, inactive, banned (computed, never stored)
Field types: text, boolean, date, number, select, url
Getting help
API keys, permissions, campus restrictions, revocation and the access log are all self-service from Developer Settings in your admin console (avatar menu, top right). For a higher daily limit or anything else, reach the DeskOS team through your usual support channel.
- Email: sales@deskos.net
- Phone: +91 742810 9069