ZetPinDevelopers

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 — format zp_app_…
  • client_secret — format zp_sec_…, shown once (confidential clients only)

Store the secret safely

The client_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.

http
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=S256

The 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:

http
GET https://yourapp.com/callback?code=zpac_a1b2c3d4...&state=8f3c9a2e

Verify state

Always confirm the returned state 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)

bash
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_xxx

Public client (PKCE, with code_verifier)

bash
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_wW1gFWFOEjXk

A successful response returns both tokens and the granted scopes:

json
{
  "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.

bash
curl https://api.zetpin.com/dev/v1/me \
  -H "Authorization: Bearer zpat_9f8e7d6c..."
json
{
  "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.

bash
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:

json
{
  "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.