MSP Partner API
Read your MSP Networks, Customer Networks, and license counts, and create new MSP Portals, with a GraphQL API.
Overview
The MSP Partner API is a GraphQL API for Twingate resellers and managed service providers. It works across every network you manage, so you can:
- List all of your MSP Portals and Customer Networks in one call.
- Pull the plan and license count for each Customer Network into your billing, PSA, or CRM.
- Create a new MSP Portal and invite its first admin from your own onboarding flow.
The API has three queries and one mutation. It cannot update or delete anything, and it cannot create Customer Networks. Create Customer Networks in the MSP Portal. They then appear in the API automatically.
MSP Partner API vs. Admin API
The MSP Partner API is scoped to your partner organization and sees across all of your networks. It does not manage what is inside a network. To manage Resources, Users, Groups, Connectors, or Security Policies in a specific network, use that network’s Admin API.
Concepts
| Concept | Description |
|---|---|
| Partner | Your organization. The API key belongs to the Partner and can see everything under it. |
| MSP Network | An MSP Portal. It has its own admins and its own Admin Console URL (https://<slug>.twingate.com). A Partner can have many MSP Networks. |
| Customer Network | One customer’s Twingate network, managed from an MSP Network. See Customer Networks. |
| Subscription | A plan type and a license count. Each Customer Network has one. Each MSP Network reports a total per plan type across its Customer Networks. |
Getting started
Credentials
Every request needs two headers. Both values are issued by Twingate. To request them, contact partnersupport@twingate.com.
| Header | Value | Notes |
|---|---|---|
X-PARTNER-ID | Your Partner ID | Identifies your Partner. Not secret. |
X-API-KEY | Your Partner API key | Secret. Shown once when issued. If you lose it, request a new key. |
Partner API keys have full access
Partner API keys have no scopes or permission levels. Any valid key can read every network under your Partner and create MSP Networks. Store the key in a secrets manager and treat it like an admin password.
Endpoint
| Item | Value |
|---|---|
| URL | Issued with your credentials. Includes your region, for example https://api.us2.twingate.com/partners |
| Protocol | GraphQL over HTTPS. Send every request, including queries, as a POST with a JSON body. GET requests return HTTP 400. |
| Headers | X-PARTNER-ID, X-API-KEY, Content-Type: application/json |
Call the API from server-side code only. It does not return CORS headers for third-party origins, so requests from a browser fail, and a key in browser code is exposed to anyone using the page.
We recommend setting a User-Agent header with your integration’s name and version, for example acme-psa-sync/2.1.0. It helps Twingate support find your requests.
First request
Use the partner query to check your credentials:
curl https://api.us2.twingate.com/partners \ -H 'Content-Type: application/json' \ -H "X-PARTNER-ID: $TWINGATE_PARTNER_ID" \ -H "X-API-KEY: $TWINGATE_PARTNER_API_KEY" \ -d '{"query":"{ partner { id name } }"}'A successful request returns your Partner record:
{"data": {"partner": {"id": "UGFydG5lcjox", "name": "Example Reseller Inc."}}}A 401 response means a header is missing or incorrect. See Troubleshooting.
GraphQL clients
GraphQL clients such as Altair, Postman, and Insomnia can send requests to the API. Set the URL to your API URL (for example https://api.us2.twingate.com/partners), add the X-PARTNER-ID and X-API-KEY headers, and load the schema for autocomplete. The schema is available in SDL and introspection formats from partnersupport@twingate.com, along with an integration playbook that includes sample queries, client code, and a local mock server.
Operations
| Operation | Kind | Arguments | Returns |
|---|---|---|---|
partner | Query | None | Partner |
networks | Query | filter, first, after, last, before | NetworkConnection |
network | Query | id: ID! | Network (MSP or Customer) or null |
mspNetworkCreate | Mutation | networkSlug: String!, networkName: String!, adminEmail: String! | MspNetworkCreateMutation! |
partner
Returns the Partner that owns the API key. Use it to check credentials or as a health check.
{ partner { id name createdAt updatedAt }}networks
Lists the MSP Networks and Customer Networks under your Partner. Disabled networks are excluded. Use filter to return one network type:
networks(filter: { networkType: { in: [MSP] } }) # MSP Networks onlynetworks(filter: { networkType: { in: [CUSTOMER_NETWORK] } }) # Customer Networks onlynetworks # Both typesnetworks returns a union of MspNetwork and CustomerNetwork. Select fields with inline fragments, and use __typename to tell the two apart:
{ networks(first: 50) { edges { node { __typename ... on MspNetwork { id name } ... on CustomerNetwork { id name parent { name } } } } }}network
Returns one network by ID, or null if the ID does not belong to your Partner. An ID of another type, or a value that is not an ID, returns an error. Unlike networks, network also returns disabled networks.
query GetNetwork($id: ID!) { network(id: $id) { __typename ... on MspNetwork { id name url } ... on CustomerNetwork { id name url subscription { planType licenses } } }}mspNetworkCreate
Creates a new MSP Network (MSP Portal) and invites its first admin.
| Argument | Description |
|---|---|
networkSlug | Becomes the subdomain of the Admin Console URL. Twingate removes every character that is not a letter or number, including hyphens, and converts the rest to lowercase. Must be unused across all of Twingate after this conversion. Cannot be changed later. |
networkName | The display name in the MSP Portal. |
adminEmail | The first admin and billing contact. Use a real mailbox, not an alias, that can sign in with Google, Microsoft, GitHub, or LinkedIn. |
mutation { mspNetworkCreate( networkSlug: "acmemsp" networkName: "Acme MSP" adminEmail: "it@acme.com" ) { ok error entity { id name url } }}The new MSP Network is on the Business plan and uses social sign-in. The admin receives a welcome email and an invitation that requires email verification.
Check the URL in the response
mspNetworkCreate does not reject a slug that contains other characters. It removes every character that is not a letter or number, converts the result to lowercase, and creates the network with that slug. The response does not say that the slug changed. For example, Acme-MSP_01 creates a network at acmemsp01.
After every successful call, read entity.url and compare its subdomain with the slug you sent. To avoid surprises, convert the slug yourself before you send it, and send only lowercase letters and numbers.
Validation errors return HTTP 200 with ok: false and a message in error, so always check ok:
{"data": {"mspNetworkCreate": {"ok": false, "error": "Network slug is taken.", "entity": null}}}error | What to do |
|---|---|
Network slug is taken. | Choose another slug. Slugs are compared after conversion, so acme-msp and AcmeMSP both conflict with an existing acmemsp. If you are retrying a request, first check whether the earlier request succeeded. |
Network slug is invalid. | No letters or numbers were left after other characters were removed. Use lowercase letters and numbers. |
Admin email is not a valid email address. | Correct the email address. |
MSP Network limit of 1000 has been reached or exceeded. | Your Partner has reached 1,000 networks. Contact partnersupport@twingate.com. |
Failed to create MSP Network | Temporary failure. Wait, confirm the network does not exist, then try again. |
Do not retry mspNetworkCreate automatically
mspNetworkCreate is not idempotent. Each successful call creates a live network and sends an email. If a request times out, convert the slug the same way Twingate does (remove every character that is not a letter or number, then convert to lowercase). Then list your MSP Networks and look for one whose url starts with https://<converted slug>. before you try again. Searching for the slug as you sent it can miss a network that was created. A second request with the same slug returns Network slug is taken. even when the first request succeeded.
Types
Partner
| Field | Type | Description |
|---|---|---|
id | ID! | The same value you send in X-PARTNER-ID. |
name | String! | The Partner name. |
createdAt | DateTime! | ISO-8601 timestamp. |
updatedAt | DateTime! | ISO-8601 timestamp. |
MspNetwork
| Field | Type | Description |
|---|---|---|
id | ID! | |
name | String! | The display name in the MSP Portal. |
url | String! | The Admin Console URL, for example https://acme-msp.twingate.com. |
aggregatedSubscriptions | [Subscription!]! | License totals across its active Customer Networks, one entry per plan type. |
customerNetworks | CustomerNetworkConnection! | Active Customer Networks. Paginated. |
adminUsers | UserConnection! | Active users with the Admin role. Paginated. |
CustomerNetwork
| Field | Type | Description |
|---|---|---|
id | ID! | |
name | String! | |
url | String! | The Admin Console URL. |
subscription | Subscription | null until the plan is set, which can take a short time after the Customer Network is created. |
parent | MspNetwork! | The MSP Network that manages this Customer Network. |
Subscription
| Field | Type | Description |
|---|---|---|
planType | PlanType! | The plan. |
licenses | Int! | Billable license count. |
licenses is the number of active users plus active Service Accounts, minus active admins, with a minimum of 1. Admins are not billed, so licenses is the billable number. See MSP Billing.
User
| Field | Type |
|---|---|
id | ID! |
email | String! |
Enums
| Enum | Values |
|---|---|
NetworkType | MSP, CUSTOMER_NETWORK |
PlanType | TEAMS, BUSINESS, BUSINESS_AND_DNSF (Business with DNS Filtering), ENTERPRISE |
IDs
IDs are opaque strings. Store them as they are returned and use them as keys in your own systems. Do not decode or construct them. Match records on IDs, not names, because names can change.
Pagination
networks, customerNetworks, and adminUsers use Relay-style cursor pagination:
query ListCustomers($after: String) { networks(filter: { networkType: { in: [CUSTOMER_NETWORK] } }, first: 50, after: $after) { totalCount pageInfo { hasNextPage endCursor } edges { node { ... on CustomerNetwork { id name subscription { planType licenses } parent { id name } } } } }}- Send the query with
first: 50and noaftervalue. - Process
edges. - If
pageInfo.hasNextPageistrue, send the query again withafterset topageInfo.endCursor. - Repeat until
hasNextPageisfalse.
The server caps page sizes, so always loop on hasNextPage instead of assuming one page holds every result. totalCount is the number of matching items across all pages. Use last and before to page backwards.
Nested connections, such as customerNetworks inside networks, paginate separately. To list every Customer Network, use the flat networks query filtered to CUSTOMER_NETWORK shown above. It needs only one loop.
Common workflows
Portfolio dashboard
Show every MSP Network with its customer count, licenses per plan, and admins:
query ListMspNetworks($after: String) { networks(filter: { networkType: { in: [MSP] } }, first: 50, after: $after) { pageInfo { hasNextPage endCursor } edges { node { ... on MspNetwork { id name url aggregatedSubscriptions { planType licenses } customerNetworks(first: 1) { totalCount } adminUsers(first: 25) { edges { node { email } } } } } } }}customerNetworks(first: 1) { totalCount } returns the customer count without fetching every customer. Cache dashboard results for a few minutes. License counts do not change from second to second.
Monthly billing export
Use the paginated Customer Network query in Pagination to write one row per customer with its MSP Network, plan, and license count.
- If
subscriptionisnull, the customer’s plan is still being set up. Mark it as pending and check again later. - To check the export, compare your totals per MSP Network with that MSP Network’s
aggregatedSubscriptions. If they differ, a customer changed during the run. Run the export again. - Save each export with the date in its file name as a record of what you billed.
Change tracking
The API returns current state only. To track changes, save a snapshot of the billing export keyed by Customer Network id on each run, and compare it with the previous snapshot:
| Change | How to detect it |
|---|---|
| New customer | The ID appears only in the new snapshot. |
| Removed customer | The ID appears only in the previous snapshot. The network was disabled or removed. |
| Plan change | Same ID, different planType. |
| License change | Same ID, different licenses. |
PSA or CRM sync
Store each Customer Network id on the matching account in your PSA or CRM, for example in a custom field. On a schedule, run the billing export query and update the license quantity on each mapped account. Skip customers whose subscription is null until the plan is set.
Provisioning MSP Networks
To create MSP Portals from your own onboarding flow:
- Convert the slug locally the same way Twingate does: remove every character that is not a letter or number, then convert to lowercase. Use the converted slug in every later step.
- List your MSP Networks and check that no
urlstarts withhttps://<converted slug>.. - Ask a person to confirm, and show them the converted slug. The call creates a live network and sends an email.
- Call
mspNetworkCreateonce with the converted slug. - Check
ok. If it isfalse, showerrorto the user. - Check that the subdomain of
entity.urlmatches the converted slug. - Save
entity.idandentity.urlagainst the account you are onboarding. - Tell the admin to expect an invitation email.
Monitoring
Every 15 to 60 minutes, send { partner { id } } from the same environment and with the same key as your scheduled jobs. Alert on a 401 response, repeated timeouts, or a null value for data.partner.
Errors and limits
Errors
| Situation | Response |
|---|---|
X-PARTNER-ID missing or invalid, or X-API-KEY missing | HTTP 401: {"status": 401, "error": "Authentication credentials were not provided."} |
| Incorrect or revoked API key | HTTP 401: {"status": 401, "error": "Incorrect authentication credentials."} |
GET request | HTTP 400. Use POST. |
HTTP method other than GET, POST, or OPTIONS | HTTP 405 |
| Invalid query, or a query nested more than 20 levels deep | HTTP 400 with an errors array. The query does not run. |
| Valid query that fails while it runs | HTTP 200 with an errors array. data can contain partial results. |
mspNetworkCreate validation failure | HTTP 200 with ok: false and a message in error |
An HTTP 200 response does not mean every field resolved. Check for errors on every response, even when data is present.
Limits
| Limit | Value |
|---|---|
| Networks per Partner | 1,000, counting MSP Networks and Customer Networks together |
| Query depth | 20 levels |
| Rate limit | None published. Keep polling modest and back off on 429 or 5xx responses. |
Retries
- Retry queries on
429,502,503,504, and network errors with exponential backoff, for example after 1, 2, and 4 seconds. - Never retry
mspNetworkCreateautomatically. Check whether the network exists first. - Set a timeout on every request. 30 seconds is a reasonable default.
Security
- Keep the API key on your server. Never include it in a browser app, mobile app, desktop tool, or a client-side environment variable such as
NEXT_PUBLIC_*. - Store the key in a secrets manager, not in source code, committed configuration files, or shared documents.
- Use a separate key for each system, for example one for billing and one for a dashboard, so you can replace one without affecting the other. To request additional keys, contact partnersupport@twingate.com.
- To rotate a key, request a new key, deploy it everywhere, confirm it works, then ask Twingate to revoke the old key. Revocation is permanent.
- If a key is exposed, contact partnersupport@twingate.com to revoke it immediately.
- Require sign-in on any internal tool that can create MSP Networks.
Troubleshooting
| Symptom | Likely cause | Fix |
|---|---|---|
401 “Authentication credentials were not provided.” | X-PARTNER-ID or X-API-KEY is missing, or the Partner ID is invalid | Check that both headers are sent with these exact names and that the Partner ID was copied in full. Some proxies remove unrecognized headers. |
401 “Incorrect authentication credentials.” | The key is incorrect or revoked, or belongs to a different Partner ID | Check the key for extra spaces or line breaks. If it is correct, contact partnersupport@twingate.com to check whether it was revoked. |
400 on a request that looks correct | The request uses GET | Use POST for every request. |
405 Method Not Allowed | The request uses a method other than GET, POST, or OPTIONS | Use POST. |
| Browser request blocked by CORS | The API is called from a web page | Call the API from your server. |
errors contains “Cannot query field” | A field name is misspelled or does not exist on that type | Check the schema. Use inline fragments with networks and network. |
| Only some customers are returned | The integration stopped after the first page | Loop until hasNextPage is false. |
| A customer has no plan | subscription is null because the plan is still being set up | Check again later. |
An error on subscription or aggregatedSubscriptions | A Customer Network is on a plan the API cannot report | Contact partnersupport@twingate.com. The flat Customer Network query still returns the other customers, but the MSP Network list can fail entirely. |
Report totals do not match aggregatedSubscriptions | A customer changed during the run | Run the report again. |
Network slug is taken. after a timeout | The first request succeeded | List your MSP Networks and look for the converted slug before you try again. |
| The new network’s URL does not match the slug you sent | The slug contained characters other than letters and numbers, or uppercase letters | Twingate converted the slug without returning an error. Use entity.url from the response as the network’s URL. |
| The new MSP admin did not receive the invitation | The email was filtered, or an alias address was used | Check spam and mail filtering. Use a real mailbox that supports a social sign-in. If the email is still missing, contact partnersupport@twingate.com. |
When you contact support, include the time and timezone, the operation name, the HTTP status, and the response body. Never include your API key.
Last updated