Free live webinar

Same team, more pipeline: AI-powered marketing for Dynamics 365 · Wed 11 Nov · 1:00pm ET · with Microsoft MVP Mark SmithSame team, more pipeline · 11 Nov

Save my seat

Getting started

Developer

This is the API for software that works with a customer's account from the outside: a migration tool that brings subscription preferences across from another system, a script that checks which integrations are connected, an assistant acting for someone on the team. It is versioned (this is v1), and every account serves it from an address of its own.

This page takes you from nothing to a first successful call. Unlike the rest of the documentation, the API pages are written for developers.

There is no credential to mint

Look for an API keys page in Settings and you will not find one. That is deliberate. Access is not a secret that someone creates and pastes into your code. It is a grant: a person in the customer's organization signs in, decides what your application may do, and the customer's administrators decide whether to let it in. What your code holds is an access token that the identity provider issues after that sign-in, and a refresh token that gets you the next one.

Three things follow, and each is a place integrators get stuck:

  • Your application sends a person through a sign-in once. After that it runs on a refresh token. If your code runs with no browser, read Using the API without a browser first; the shortest path there is a command-line tool that does the sign-in for you.
  • A sign-in is not access. The customer's administrators control which applications are let in. Your first call may be answered with a request to wait for approval, which is expected, not an error in your code (see step 4).
  • Every customer is a separate grant. A token is for one organization. If you work with twenty customers you hold twenty grants, each approved by that customer and each revocable by that customer alone.

Before you start

You need:

  • The customer's base URL, https://<tenant-host>/api/public/v1. Each account has its own host, so ask the customer for it. Every path on these pages is relative to it, except where a page says otherwise.
  • An OAuth 2.0 client that can run the authorization-code flow with PKCE. A library for your language does the work; Authentication lists exactly what to send.
  • A client identity. Your application identifies itself with a URL that serves a small metadata document. Authentication says how.
  • A person in the customer's organization who can sign in for the first time. Where the customer's access policy requires it, an administrator also has to approve the application.

Step 1: find out where to sign in

The API publishes unauthenticated metadata that names the authorization server and says what to ask for:

curl -sS https://acme.example.com/.well-known/oauth-protected-resource/api/public/v1
{
  "resource": "https://acme.example.com/api/public/v1",
  "authorization_servers": ["https://clerk.example.com"],
  "scopes_supported": [
    "contacts:read", "contacts:write", "consent:read", "consent:write",
    "subscription-lists:read", "subscription-lists:write",
    "email-templates:read", "email-templates:write", "integrations:read",
    "user:org:read"
  ],
  "bearer_methods_supported": ["header"]
}

Note where the document lives. It is not under /api/public/v1: the well-known segment goes between the host and the path of the thing it describes, so the address is the host, then /.well-known/oauth-protected-resource, then /api/public/v1. A client that tacks the segment onto the end of the base URL gets a 404.

Step 2: sign in

Send the person who is signing in to the authorization server's authorization_endpoint with PKCE, the scopes you need, and the resource value from the document above. Authentication lists every parameter and explains the three that people forget: resource, user:org:read and offline_access.

Ask for every scope your application will ever need, in this first request. What an administrator approves is the set you held when you first connected, and it is not widened afterwards (see Scopes).

The result is an access token. Treat it as an opaque string.

Step 3: make the first call

GET /meta needs a valid token and no particular scope (it does need the organization to have let your application in; see step 4). It reports which tenant and which credential the API believes you are, so it is the right first call:

curl -sS https://acme.example.com/api/public/v1/meta \
  -H "Authorization: Bearer $TOKEN"
{
  "api_version": "v1",
  "tenant_id": "tnt_0123456789abcdef",
  "scopes": ["consent:read", "integrations:read"],
  "client": {
    "id": "https://migrate.example.org/client.json",
    "name": "https://migrate.example.org/client.json"
  },
  "server_time": "2026-10-16T09:30:00.123456+00:00"
}

A 200 here is confirmation that the token is for this tenant: a token issued for a different organization never reaches /meta, it is refused with tenant_mismatch. Record tenant_id if you need to remember which tenant you confirmed, and treat it as an opaque string; the route reference explains why.

Every response, errors included, carries an X-Tstr-Api-Version header. For this API it is v1.

Step 4: if the answer is 403

Not every 403 means something is wrong with your code. Four of them mean the customer's side has to act, and the right reaction to those is to tell a person, not to retry or to sign in again:

typeWhat it meansWhat to do
grant_pending_approvalThe application connected and is waiting for an administrator.Ask an administrator to approve it in Settings, on the Connected applications tab. The same token works once they do.
third_party_access_admins_onlyThe organization only lets administrators connect applications, and the person who signed in is not one.An administrator has to approve the application in Settings. Signing in again, as anyone, does not change the answer.
grant_deniedAn administrator declined this application.Talk to the customer. It stays declined until an administrator approves it.
third_party_access_disabledThe organization has switched third-party access off.Nothing is served to any application, approved ones included, until an administrator turns it back on.

The full list of error types, with the two 403s that are about your request (tenant_mismatch and insufficient_scope), is in Conventions.

Step 5: check what you were given

The scopes in the /meta response are the scopes you can actually use. They can be fewer than the ones you asked for: an application that asks for more than its administrator approved gets the approved set, and a member who is not an Admin cannot delegate write scopes. If a scope you need is missing, Scopes says where to look. Compare before you start work, not when the first write fails.

Where to go next