Skip to content

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 (version v1, echoed in x-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

  1. Create a random code_verifier (43–128 chars) and its S256 challenge. PKCE and state are required.
  2. Send the person to the authorize URL. Ask Ozwal shows its own consent screen; sensitive items are approved separately.
  3. Your redirect receives ?code=…&state=… (or error=access_denied). Codes expire in 60 s and are single use.
  4. 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.
  5. 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_ID

Scopes & 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.

EndpointRequires
GET /health—

No authentication.

GET /scopes—

No authentication. This document.

GET /meprofile.basic.read
GET /me/profileprofile.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/preferencespreferences.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=foodpreferences.food.read or preferences.read
GET /me/preferences?category=travelpreferences.travel.read or preferences.read
GET /me/preferences?category=entertainmentpreferences.entertainment.read or preferences.read
GET /me/preferences?category=hobbiespreferences.hobbies.read or preferences.read
GET /me/likeslikes.read
GET /me/dislikesdislikes.read
GET /me/goalsgoals.read
GET /me/valuesvalues.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 /personalizepersonalization.create
POST /matchpersonalization.create
POST /recommendationsrecommendations.create
All scopes (27)
  • profile.basic.read — See your name and account basics
  • preferences.read — See your preferences
  • preferences.food.read — See your food and dining preferences
  • preferences.travel.read — See your travel preferences
  • preferences.entertainment.read — See your entertainment preferences
  • preferences.hobbies.read — See your hobbies and interests
  • likes.read — See relevant things you like
  • dislikes.read — See relevant things you dislike
  • goals.read — See your stated goals
  • values.read — See what you say matters to you
  • recommendations.create — Ask Ozwal for recommendations for you
  • personalization.create — Ask Ozwal to personalize its own results for you
  • context.food.read — Use the minimum dining context needed for a task
  • context.travel.read — Use the minimum travel context needed for a task
  • context.entertainment.read — Use the minimum entertainment context needed for a task
  • context.inspiration.read — Use the minimum interests and tastes needed to suggest things you might enjoy
  • travel.accessibility.read — Share the accessibility requirements needed to offer suitable travel options
  • food.allergies.read — Share the foods you must avoid so unsuitable options are ruled out
  • itinerary.context.read — Use the minimum travel context needed to personalize a flight search or itinerary
  • actions.reservations.create — Prepare a reservation for you
  • actions.reservations.read — See reservations prepared for you
  • actions.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 you
  • reflections.read — Read your private reflections
  • assessments.read — Read your private assessment results
  • traits.read — Read your inferred personality traits

Categories

GET /me/preferences?category= accepts:

  • food → preferences.food.read
  • travel → preferences.travel.read
  • entertainment → preferences.entertainment.read
  • hobbies → 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's cf-ray value through Support so we can adjust the rule.