Ask Ozwal API v1
Let people bring their Ask Ozwal taste profile into your app — only what they approve, only for the job your app does.
Overview
- Base URL:
https://api.askozwal.com/api/public/v1(versionv1, echoed inx-ozwal-api-version) - JSON only. Success:
{ "data": …, "meta": { "request_id": … } } - Machine-readable scope contract:
GET https://api.askozwal.com/api/public/v1/scopes(no auth) - Health:
GET https://api.askozwal.com/api/public/v1/health
Access & registration
Registration is by arrangement during the pilot — there is no self-service portal yet. Contact us through Support with your app name, company, purpose, exact HTTPS redirect URIs and the scopes you need. You receive a client_id. Public clients use PKCE and have no client secret. Your registration is the ceiling: requests can never widen it.
OAuth authorization code + PKCE
- Create a random
code_verifier(43–128 chars) and its S256 challenge. PKCE andstateare required. - Send the person to the authorize URL. Ask Ozwal shows its own consent screen; sensitive items are approved separately.
- Your redirect receives
?code=…&state=…(orerror=access_denied). Codes expire in 60 s and are single use. - Exchange the code. Access tokens last 900 s; refresh tokens last 30 days and rotate on every use. Reusing a code or an old refresh token ends the connection.
- Disconnect: the person can disconnect in Settings → Connected apps at any time, or you can call
POST /oauth/revoke.
GET https://api.askozwal.com/api/public/v1/oauth/authorize
?client_id=YOUR_CLIENT_ID
&redirect_uri=https://yourapp.example/callback
&response_type=code
&scope=profile.basic.read%20preferences.food.read
&state=RANDOM_STATE
&code_challenge=BASE64URL_SHA256_OF_VERIFIER
&code_challenge_method=S256
POST https://api.askozwal.com/api/public/v1/oauth/token (application/x-www-form-urlencoded)
grant_type=authorization_code&code=CODE&client_id=YOUR_CLIENT_ID
&redirect_uri=https://yourapp.example/callback&code_verifier=VERIFIER
POST https://api.askozwal.com/api/public/v1/oauth/token
grant_type=refresh_token&refresh_token=CURRENT_REFRESH_TOKEN&client_id=YOUR_CLIENT_ID
POST https://api.askozwal.com/api/public/v1/oauth/revoke
token=ACCESS_OR_REFRESH_TOKEN&client_id=YOUR_CLIENT_IDScopes & endpoints
Broad scopes imply narrower ones (preferences.read covers every preferences.*.read); narrow scopes never imply broad ones. Private reflections, assessments and traits are never included in any broad grant. To see what your token holds, call GET /me and read granted_scopes.
| Endpoint | Requires |
|---|---|
GET /health | — No authentication. |
GET /scopes | — No authentication. This document. |
GET /me | profile.basic.read |
GET /me/profile | profile.basic.read AND preferences.read Aggregate endpoint: needs BOTH scopes. Category sub-scopes alone are not enough — use /api/public/v1/me/preferences?category=… instead. |
GET /me/preferences | preferences.read Without ?category the aggregate scope preferences.read is required. With ?category the matching category scope (or preferences.read) is enough. |
GET /me/preferences?category=food | preferences.food.read or preferences.read |
GET /me/preferences?category=travel | preferences.travel.read or preferences.read |
GET /me/preferences?category=entertainment | preferences.entertainment.read or preferences.read |
GET /me/preferences?category=hobbies | preferences.hobbies.read or preferences.read |
GET /me/likes | likes.read |
GET /me/dislikes | dislikes.read |
GET /me/goals | goals.read |
GET /me/values | values.read |
GET /me/context | — Needs ?purpose=… ; the required scope depends on the purpose (see purposes). The app's contract must also allow that purpose. |
POST /personalize | personalization.create |
POST /match | personalization.create |
POST /recommendations | recommendations.create |
All scopes (27)
profile.basic.read— See your name and account basicspreferences.read— See your preferencespreferences.food.read— See your food and dining preferencespreferences.travel.read— See your travel preferencespreferences.entertainment.read— See your entertainment preferencespreferences.hobbies.read— See your hobbies and interestslikes.read— See relevant things you likedislikes.read— See relevant things you dislikegoals.read— See your stated goalsvalues.read— See what you say matters to yourecommendations.create— Ask Ozwal for recommendations for youpersonalization.create— Ask Ozwal to personalize its own results for youcontext.food.read— Use the minimum dining context needed for a taskcontext.travel.read— Use the minimum travel context needed for a taskcontext.entertainment.read— Use the minimum entertainment context needed for a taskcontext.inspiration.read— Use the minimum interests and tastes needed to suggest things you might enjoytravel.accessibility.read— Share the accessibility requirements needed to offer suitable travel optionsfood.allergies.read— Share the foods you must avoid so unsuitable options are ruled outitinerary.context.read— Use the minimum travel context needed to personalize a flight search or itineraryactions.reservations.create— Prepare a reservation for youactions.reservations.read— See reservations prepared for youactions.simulated.dining.propose— Ask you to approve a simulated restaurant reservation (a test — no real table is booked)actions.simulated.travel.propose— Ask you to approve a simulated itinerary hold (a test — nothing is really held or bought)agents.invoke— Ask an Ozwal agent to carry out a task for youreflections.read— Read your private reflectionsassessments.read— Read your private assessment resultstraits.read— Read your inferred personality traits
Categories
GET /me/preferences?category= accepts:
food→preferences.food.readtravel→preferences.travel.readentertainment→preferences.entertainment.readhobbies→preferences.hobbies.read
Omitting category is the aggregate call and needs preferences.read. An unknown value returns 400 invalid_request listing the valid values. /me/profile is aggregate too: it needs both profile.basic.read and preferences.read.
Errors
HTTP/1.1 403
WWW-Authenticate: Bearer error="insufficient_scope", scope="preferences.read"
{ "error": {
"code": "insufficient_scope",
"message": "This application does not have permission to access this resource.",
"request_id": "req_…",
"required_scopes": { "all_of": [ { "any_of": ["preferences.read"] } ] },
"missing_scopes": [ { "any_of": ["preferences.read"] } ],
"documentation_url": "https://www.askozwal.com/developers#scopes"
} }all_of entries must all be met; within each, any listed scope is enough. Other codes: 400 invalid_request, 401 invalid_token, 403 purpose_not_permitted, 404 not_found, 405 method_not_allowed, 429 rate_limited, 503 feature_disabled, 500 server_error. OAuth endpoints use the standard { "error", "error_description" } shape. Always quote request_id to support. Endpoints are not paginated.
Rate limits
Per application, person and IP: reads 60/min, intelligence (personalize, match, recommendations) 20/min, token and revoke 10/min, health and scopes 120/min. Responses carry x-ratelimit-limit, x-ratelimit-remaining and x-ratelimit-reset; a 429 carries retry-after seconds.
Examples
curl
curl -H "Authorization: Bearer $ACCESS_TOKEN" \
"https://api.askozwal.com/api/public/v1/me/preferences?category=food"JavaScript (fetch)
const res = await fetch("https://api.askozwal.com/api/public/v1/me/preferences?category=food", {
headers: { Authorization: `Bearer ${accessToken}` },
});
const body = await res.json();
if (!res.ok) throw new Error(`${body.error.code}: ${body.error.message} (${body.error.request_id})`);
console.log(body.data.preferences);Python (requests)
import requests
r = requests.get("https://api.askozwal.com/api/public/v1/me/preferences",
params={"category": "food"},
headers={"Authorization": f"Bearer {access_token}"}, timeout=10)
body = r.json()
if not r.ok:
raise RuntimeError(body["error"])
print(body["data"]["preferences"])Troubleshooting
- 401 invalid_token
- Token expired (15 min), revoked, or the person disconnected. Refresh once; if that fails, re-authorize.
- 403 insufficient_scope
- Read
required_scopes. Category scopes only work with?category=; aggregate endpoints need the broad scope. New scopes must be registered and consented to again. - 400 invalid_request on category
- Use one of: food, travel, entertainment, hobbies.
- HTML page or 404 instead of JSON
- Check the path includes
/api/public/v1/. The short/v1/…form is not served. - Edge block (e.g. Cloudflare error 1010)
- This comes from the network edge, before Ozwal sees the request. Send a normal
User-Agent, and send us the response'scf-rayvalue through Support so we can adjust the rule.
