Quickstart
Go from zero to a published post. This walkthrough covers getting credentials, running the OAuth flow, exchanging the code for tokens, and calling the API — all with copy-pasteable curl.
0. Get your credentials
App registration is admin-managed today (manual partner onboarding). Email developers@zetpin.com with your app name, redirect URI(s), and the scopes you need. You'll receive:
client_id— formatzp_app_…client_secret— formatzp_sec_…, shown once (confidential clients only)
Store the secret safely
Theclient_secret is shown exactly once at registration and only its sha256 hash is stored on the server. If you lose it, you'll need a secret rotation. Never ship it in a mobile or browser app — those are public clients and must use PKCE instead.1. Send the user to the authorize URL
Redirect the user's browser to /oauth/authorize. Include a random state value for CSRF protection and — for public clients — a PKCE code_challenge. Scopes are space-separated.
GET https://api.zetpin.com/oauth/authorize
?client_id=zp_app_xxx
&redirect_uri=https://yourapp.com/callback
&response_type=code
&scope=profile.read posts.write posts.read
&state=8f3c9a2e
&code_challenge=E9Melhoa2OwvFrEMTJguCHaoeK1t8URWbuGJSstw-cM
&code_challenge_method=S256The user signs in with a phone OTP on ZetPin's consent page and approves. ZetPin then redirects back to your redirect_uri with a single-use authorization code (prefix zpac_, 5-minute TTL) and your original state:
GET https://yourapp.com/callback?code=zpac_a1b2c3d4...&state=8f3c9a2eVerify state
Always confirm the returnedstate matches the value you sent before using the code. A mismatch means the request may have been forged — discard it.2. Exchange the code for tokens
POST the code to /oauth/token with grant_type=authorization_code. Confidential clients authenticate with their client_secret; public clients send the PKCE code_verifier that matches the challenge from step 1.
Confidential client (server-side, with secret)
curl -X POST https://api.zetpin.com/oauth/token \
-H "Content-Type: application/x-www-form-urlencoded" \
-d grant_type=authorization_code \
-d code=zpac_a1b2c3d4... \
-d redirect_uri=https://yourapp.com/callback \
-d client_id=zp_app_xxx \
-d client_secret=zp_sec_xxxPublic client (PKCE, with code_verifier)
curl -X POST https://api.zetpin.com/oauth/token \
-H "Content-Type: application/x-www-form-urlencoded" \
-d grant_type=authorization_code \
-d code=zpac_a1b2c3d4... \
-d redirect_uri=https://yourapp.com/callback \
-d client_id=zp_app_xxx \
-d code_verifier=dBjftJeZ4CVP-mB92K27uhbUJU1p1r_wW1gFWFOEjXkA successful response returns both tokens and the granted scopes:
{
"access_token": "zpat_9f8e7d6c...",
"token_type": "Bearer",
"expires_in": 3600,
"refresh_token": "zprt_1a2b3c4d...",
"scope": "profile.read posts.write posts.read"
}3. Call GET /dev/v1/me
Confirm the token works by reading the authorizing user's profile. Pass the access token as a Bearer credential.
curl https://api.zetpin.com/dev/v1/me \
-H "Authorization: Bearer zpat_9f8e7d6c..."{
"id": "usr_7hk2...",
"name": "Aditi Rao",
"photoUrl": "https://cdn.zetpin.com/u/7hk2.jpg",
"karma": 428,
"neighborhood": "Indiranagar, Bengaluru"
}4. Publish a post
POST to /dev/v1/posts with the posts.write scope. You must supply at least one of title, content, or images. If you omit latitude/longitude, the post falls back to the user's saved neighborhood.
curl -X POST https://api.zetpin.com/dev/v1/posts \
-H "Authorization: Bearer zpat_9f8e7d6c..." \
-H "Content-Type: application/json" \
-d '{
"content": "Fresh sourdough at the Saturday market this weekend ☀️",
"tags": ["food", "market"],
"reachKm": 3,
"visibility": "public"
}'A successful publish returns 201 Created with the new post and its public permalink:
{
"id": "post_a1b2c3",
"content": "Fresh sourdough at the Saturday market this weekend ☀️",
"tags": ["food", "market"],
"reachKm": 3,
"visibility": "public",
"permalink": "https://zetpin.com/post/post_a1b2c3",
"createdAt": "2026-07-08T09:14:22.000Z"
}You're done
That's the full loop. From here, read Refreshing & revoking to keep access alive past the 1-hour token TTL, and the Endpoints reference for every route and response shape.