Skip to main content
Every API call requires both authentication layers. Missing or invalid credentials result in a 401 or 403 — see Errors.
HeaderPurposeSource
x-api-keyIdentifies your partner applicationProvisioned by Daysync
x-api-secretAuthenticates your partner applicationProvisioned by Daysync (stored bcrypt-hashed)
Authorization: Bearer <token>Identifies the Daysync user the call acts forOAuth 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:
FieldExample
API Keydk_live_…
API Secretds_…
ScopesThe set of scopes your integration needs (see Scopes)
ExpiresCredential 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:
SettingValue
Authorize URLhttps://integrations.daysync.com/authorize
Token URLhttps://integrations.daysync.com/api/oauth/token
Client IDYour API key (dk_live_…)
Client secretYour API secret (ds_…)
Grant typeAuthorization Code with PKCE (S256 — required)
Redirect URIA callback URI you host, registered self-service on your app page (see below)
Client authenticationHTTP 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

FieldValue
TypeOAuth 2.0
Grant TypeAuthorization Code (With PKCE)
Auth URLhttps://integrations.daysync.com/authorize
Access Token URLhttps://integrations.daysync.com/api/oauth/token
Client IDYour API key (dk_live_…)
Client SecretYour API secret (ds_…)
Code Challenge MethodSHA-256
Scopeopenid email daysync-api/tours.read daysync-api/organization.read
Client AuthenticationSend 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.)