Every API call requires both authentication layers. Missing or invalid credentials result in a 401 or 403 — see Errors.
| Header | Purpose | Source |
|---|
x-api-key | Identifies your partner application | Provisioned by Daysync |
x-api-secret | Authenticates your partner application | Provisioned by Daysync (stored bcrypt-hashed) |
Authorization: Bearer <token> | Identifies the Daysync user the call acts for | OAuth flow (below) |
- The partner key/secret is a long-lived credential issued to your app. Treat the secret like a password: it is shown once at provisioning time and stored only as a bcrypt hash, so it cannot be recovered — only rotated.
- The user token is a short-lived (≈1 hour) OAuth access token that represents the Daysync user who authorized your app. All data access is scoped to what that user can see and do in Daysync.
The partner credential carries your granted scopes, which gate endpoint access. See Scopes & Permissions.
Partner credentials
Partner credentials are issued through the Daysync Integrations Portal. A credential has:
- an API key (
dk_live_…) sent in x-api-key,
- an API secret (
ds_…) sent in x-api-secret,
- a set of scopes,
- an optional expiry.
A credential is rejected if it is unknown, revoked, or expired. Note that a present-but-invalid API key is rejected by the gateway authorizer with 403 Forbidden (a bare { "message": "Forbidden" } body) — only a missing API key returns 401. See Errors.
Obtaining credentials
Apply for partner credentials in the Integrations Portal. Once your application is approved you receive:
| Field | Example |
|---|
| API Key | dk_live_… |
| API Secret | ds_… |
| Scopes | The set of scopes your integration needs (see Scopes) |
| Expires | Credential expiry date (if applicable) |
Partner credentials are shown once at provisioning time and stored as a bcrypt hash. They cannot be recovered — only rotated. Keep them in a secure vault.
User token — OAuth 2.0 Authorization Code flow (with PKCE)
Daysync users authorize your app through the Daysync Integrations Portal: a branded Daysync sign-in followed by a consent screen listing the permissions you request. Your backend then exchanges the returned authorization code for tokens.
Your existing partner credential doubles as your OAuth client — there is no separate OAuth registration:
| Setting | Value |
|---|
| Authorize URL | https://integrations.daysync.com/authorize |
| Token URL | https://integrations.daysync.com/api/oauth/token |
| Client ID | Your API key (dk_live_…) |
| Client secret | Your API secret (ds_…) |
| Grant type | Authorization Code with PKCE (S256 — required) |
| Redirect URI | A callback URI you host, registered self-service on your app page (see below) |
| Client authentication | HTTP Basic auth header (api_key:api_secret) |
PKCE is mandatory. Every authorize request must carry code_challenge (S256) and every token exchange the matching code_verifier. Requests without S256 PKCE are rejected with invalid_request.
Redirect (callback) URI
The OAuth flow requires a redirect URI — it’s where Daysync sends the user after they approve access, appending the authorization ?code=… that your backend exchanges for tokens.
Register it yourself, instantly. Open your app in the Integrations Portal, add your callback under Redirect URIs, and save — the allowlist takes effect immediately, no Daysync review needed. HTTPS is required (http://localhost is permitted for development). An authorize request whose redirect_uri is not on your app’s list is rejected before any redirect happens (“This application’s redirect URL is not registered”).
The authorization code is delivered to whatever URL you pass as redirect_uri, so only a URL you control lets your backend capture the code and exchange it for an access token.
Requestable scopes
Request scopes space-separated in the scope parameter — ask only for what you need. The two OpenID scopes plus the 14 Daysync API scopes are available:
openid
email
daysync-api/accommodation.read daysync-api/accommodation.write
daysync-api/bulletins.read daysync-api/bulletins.write
daysync-api/guestlist.read daysync-api/guestlist.write
daysync-api/organization.read
daysync-api/schedule.read daysync-api/schedule.write
daysync-api/tours.read daysync-api/tours.write
daysync-api/users.read
daysync-api/venues.read daysync-api/venues.write
The consented scope is clamped to what your partner credential is granted: scopes you request beyond your credential’s grant are silently dropped from the consent screen and the issued token. Endpoint access is ultimately gated by your partner scopes (see Scopes & Permissions).
Step 1 — Redirect the user to the portal
Generate a PKCE pair (code_verifier = random 43–128 chars; code_challenge = base64url(SHA-256(verifier))), then send the user to:
https://integrations.daysync.com/authorize
?response_type=code
&client_id=dk_live_YOUR_API_KEY
&redirect_uri=YOUR_REDIRECT_URI
&scope=openid+email+daysync-api/tours.read+daysync-api/organization.read
&state=RANDOM_STATE
&code_challenge=YOUR_CODE_CHALLENGE
&code_challenge_method=S256
The user signs in with their normal Daysync account, then sees a consent screen naming your app and the requested permissions.
Step 2 — Receive the authorization code
After the user approves, the browser is redirected to your redirect_uri with ?code=AUTH_CODE&state=… appended. If they decline, you receive ?error=access_denied instead. Codes are short-lived (5 minutes) — exchange promptly.
Step 3 — Exchange the code for tokens
Authenticate the client with HTTP Basic auth (-u) using your API key and secret, and include the PKCE code_verifier:
curl -X POST https://integrations.daysync.com/api/oauth/token \
-H "Content-Type: application/x-www-form-urlencoded" \
-u "dk_live_YOUR_API_KEY:ds_YOUR_API_SECRET" \
-d "grant_type=authorization_code" \
-d "code=AUTH_CODE" \
-d "redirect_uri=YOUR_REDIRECT_URI" \
-d "code_verifier=YOUR_CODE_VERIFIER"
Response:
{
"access_token": "eyJraWQ...",
"refresh_token": "eyJjdHk...",
"token_type": "Bearer",
"expires_in": 3412,
"scope": "daysync-api/tours.read daysync-api/organization.read"
}
Use the access_token as your Authorization: Bearer token, and treat scope as the authoritative record of what the user consented to.
expires_in reflects the actual remaining life of the issued token and can be shorter than the nominal hour. Track it and refresh when it approaches zero rather than assuming 3600.
Step 4 — Refresh the token
Access tokens expire after ≈1 hour; the refresh token is valid for 30 days. Exchange a valid refresh token for a new access token (no user interaction needed):
curl -X POST https://integrations.daysync.com/api/oauth/token \
-H "Content-Type: application/x-www-form-urlencoded" \
-u "dk_live_YOUR_API_KEY:ds_YOUR_API_SECRET" \
-d "grant_type=refresh_token" \
-d "refresh_token=<REFRESH_TOKEN>"
The response has the same shape as Step 3 (no scope field). If the refresh token itself has expired, you receive 400 invalid_grant — restart the flow from Step 1.
Calling the API
Once you have an access token, every API call needs the bearer token and your partner key/secret:
curl -i "https://integration.daysync.com/v1/tours?org_id=<ORG_ID>" \
-H "Authorization: Bearer <access_token>" \
-H "x-api-key: <API_KEY>" \
-H "x-api-secret: <API_SECRET>"
Postman setup
| Field | Value |
|---|
| Type | OAuth 2.0 |
| Grant Type | Authorization Code (With PKCE) |
| Auth URL | https://integrations.daysync.com/authorize |
| Access Token URL | https://integrations.daysync.com/api/oauth/token |
| Client ID | Your API key (dk_live_…) |
| Client Secret | Your API secret (ds_…) |
| Code Challenge Method | SHA-256 |
| Scope | openid email daysync-api/tours.read daysync-api/organization.read |
| Client Authentication | Send as Basic Auth header |
Remember to add your Postman callback URL (https://oauth.pstmn.io/v1/callback) to your app’s Redirect URIs in the portal first. After Postman completes the OAuth exchange, set the obtained access_token as a Bearer token and add your x-api-key / x-api-secret headers, then call the API.
How the token is validated
The API verifies the bearer token on every call:
- signature against the published JWKS,
- issuer matches the Daysync user pool,
token_use is access,
- token has not expired.
A failed check returns 401.
CORS
The API answers OPTIONS preflight requests with 204 before any authentication, and successful API responses include Access-Control-Allow-Origin: *.
Browser (cross-origin) clients are not currently supported. Two gateway issues block calling the API directly from a browser:
- The preflight’s
Access-Control-Allow-Headers lists authorization, content-type, and x-api-key but not x-api-secret — a header every call must send. The browser blocks the request at preflight.
- When a preflight includes an
Access-Control-Request-Headers header (which browsers always send when requesting custom headers like x-api-key/x-api-secret), the API returns 204 with no Access-Control-Allow-* headers at all, so the browser blocks the actual request.
To continue use of the API, call the API server-to-server rather than from a browser. (Sending the partner x-api-secret from browser code is also poor practice.)