1. Discovery
Everything starts from the metadata. MCP apps find it through the 401 answer of /mcp.
| Address | What |
|---|---|
https://app.postozo.com/.well-known/oauth-authorization-server | Authorization server metadata (RFC 8414) |
https://app.postozo.com/.well-known/oauth-protected-resource | Protected resource metadata for the MCP server (RFC 9728). Also at /.well-known/oauth-protected-resource/mcp. |
{
"issuer": "https://app.postozo.com",
"authorization_endpoint": "https://app.postozo.com/oauth/authorize",
"token_endpoint": "https://app.postozo.com/oauth/token",
"registration_endpoint": "https://app.postozo.com/oauth/register",
"revocation_endpoint": "https://app.postozo.com/oauth/revoke",
"device_authorization_endpoint": "https://app.postozo.com/oauth/device/code",
"response_types_supported": [
"code"
],
"grant_types_supported": [
"authorization_code",
"urn:ietf:params:oauth:grant-type:device_code"
],
"code_challenge_methods_supported": [
"S256"
],
"token_endpoint_auth_methods_supported": [
"none",
"client_secret_post"
],
"scopes_supported": [
"mcp"
]
}
2. Register your app
Dynamic client registration (RFC 7591). No account is needed. Limit: 30 an hour per IP address.
curl -X POST "https://app.postozo.com/oauth/register" \
-H "Content-Type: application/json" \
-d '{"client_name": "My app", "redirect_uris": ["https://example.com/callback"]}'
{
"client_id": "plc_rb3RYj6DyEktL7Jb",
"client_name": "My app",
"redirect_uris": [
"https://example.com/callback"
],
"grant_types": [
"authorization_code"
],
"response_types": [
"code"
],
"token_endpoint_auth_method": "none",
"client_id_issued_at": 1790706315
}
redirect_urisis required. Each must be https, http only on localhost, 127.0.0.1 or [::1], or your app's own scheme, for examplecursor://...,vscode://...,claude://...orcom.example.app:/callback. http on any other host is refused, and so are other web and script schemes (ws:,wss:,ftp:,file:,javascript:,data:,blob:and others) and addresses with a#part. A refused address gets 400{"error": "invalid_redirect_uri", "error_description": "http is only allowed on localhost, 127.0.0.1 or [::1]. Use https: http://example.com/callback", "msg": "..."}.client_nameis shown on the Allow screen (up to 100 characters).- The default is a public app with no secret (
token_endpoint_auth_method: "none"). Send"token_endpoint_auth_method": "client_secret_post"to get aclient_secret(pls_...), shown only in this answer. - The answer is 201. Save the client_id; there is no endpoint to read or change it later. Register again to change the redirect addresses.
3. Send the person to sign in (PKCE)
Make a random code_verifier, and its code_challenge: the SHA-256 of the verifier, base64url without padding. Then open:
https://app.postozo.com/oauth/authorize?response_type=code&client_id=plc_rb3RYj6DyEktL7Jb&redirect_uri=https%3A%2F%2Fexample.com%2Fcallback&code_challenge=CHALLENGE&code_challenge_method=S256&state=RANDOM
response_typemust becode;code_challengeis required and the method must beS256;redirect_urimust be 1 of the registered addresses, exactly.- The person signs in if needed, sees "Allow My app to use Postozo?" with what it can do, and presses Allow or Cancel.
- Allow sends them to
redirect_uri?code=...&state=.... Cancel sendserror=access_denied. Always checkstate. - The code works once and for 5 minutes.
4. Get the token
curl -X POST "https://app.postozo.com/oauth/token" \
-H "Content-Type: application/x-www-form-urlencoded" \
-d "grant_type=authorization_code&code=CODE&client_id=plc_rb3RYj6DyEktL7Jb&redirect_uri=https://example.com/callback&code_verifier=VERIFIER"
{
"access_token": "pz_oat_...",
"token_type": "Bearer",
"scope": "mcp"
}
The body can be form data or JSON. Apps with a secret add client_secret. The answer has Cache-Control: no-store. Errors are standard OAuth JSON with msg added (the same text as error_description), for example 400 {"error": "invalid_grant", "error_description": "PKCE check failed", "msg": "PKCE check failed"}. A failed attempt uses up the code, so start again from step 3. Limit: 300 token requests an hour per IP address.
5. Use the token
curl "https://app.postozo.com/public/v1/integrations" -H "Authorization: Bearer pz_oat_..."
The token works on the public API and the MCP server. It acts as the person who approved, with their role in the workspace they were in. It has no expiry date.
Scopes
There is 1 scope, mcp, and every token gets it. It means: see the workspace's channels and posts and schedule posts, as the person could in the app. There are no read-only tokens yet; to limit what a token can do, have a person with a smaller role (for example an editor) approve it.
Revoke
- The person: Settings, Approved Apps, Revoke. It works at once.
- Your app:
POST /oauth/revokewith{"token": "pz_oat_..."}(JSON or form). The answer is always 200{}, also for unknown tokens.
Device flow (for apps without a browser)
This is how the CLI signs in. No registration is needed.
curl -X POST "https://app.postozo.com/oauth/device/code"
{
"device_code": "LcQTPvnhPPykIrjAIF9_ZVKZCIQfjXbKEU-WUTt_H2w",
"user_code": "GRGG-WWDV",
"verification_uri": "https://app.postozo.com/device",
"verification_uri_complete": "https://app.postozo.com/device?code=GRGG-WWDV",
"expires_in": 600,
"interval": 3
}
Show the person the link and the code. Then ask for the token every interval seconds:
curl -X POST "https://app.postozo.com/oauth/token" \
-H "Content-Type: application/json" \
-d '{"grant_type": "urn:ietf:params:oauth:grant-type:device_code", "device_code": "LcQTPvnhPPykIrjAIF9_ZVKZCIQfjXbKEU-WUTt_H2w"}'
| Answer | Meaning |
|---|---|
400 authorization_pending | Not approved yet. Ask again after the interval. |
400 access_denied | The person pressed Deny. |
400 expired_token | The 10 minutes are over. Start again. |
400 invalid_grant | Unknown device code, or the token was already collected. |
200 | { "access_token": "pz_cli_...", "token_type": "Bearer", "scope": "mcp" }. The token shows as "Command line" in Approved Apps. |
Limit: 30 device codes an hour per IP address.
Questions
Do I need to register my app by hand?
No. Register it with 1 request to /oauth/register. There is no developer console.
Are there refresh tokens?
No. Access tokens do not expire. They stop working when the person revokes the app in Settings, Approved Apps, or when you call /oauth/revoke.
What can a token do?
Everything the person can do in that workspace with their role, through the public API and the MCP server. There is 1 scope: mcp.
Which workspace does the token use?
The workspace the person was in when they pressed Allow. To use another workspace, they switch in the app and sign in to your app again.
Is PKCE required?
Yes, with S256, for every app, including apps with a client secret.
Get your API key
Start a 7-day free trial. The API, MCP server, webhooks and CLI are in every plan.
Create your free account