Choose an integration type and authenticate

Choose between a private API-key integration and a multi-customer OAuth app, then make your first authenticated Wafeq request.

Choose an integration type and authenticate

Wafeq supports two ways to authenticate to the Public API. Choose the model that matches who will use your integration; both use the same API endpoints at https://api.wafeq.com/v1.

Choose how to connect

API key integrationOAuth app
Best forA private integration for your own organizationA product or platform that connects Wafeq customers
OrganizationsOne organization per keyMany customer organizations
Customer consentNot requiredRequired for each connected organization
Access modelOrganization-wideLeast-privilege OAuth scopes
Request headerApi-Key <API_KEY>Bearer <ACCESS_TOKEN>
Manage itAPI Keys in the connected Wafeq organizationWafeq Developer Portal
📘

Same accounting API

Authentication changes how a connection is established. It does not change invoice, bill, payment, banking, journal, file, or reporting schemas.

API key integration

Use an API key when the integration is private, runs on trusted server-side infrastructure, and connects one Wafeq organization.

Create the key from the Wafeq application's API Keys settings while the intended accounting organization is active. Send it on every request:

Authorization: Api-Key <API_KEY>

Protect the key like a password:

  • Store it in a server-side secret manager.
  • Do not put it in frontend, mobile, analytics, support, or log payloads.
  • Use separate keys for separate organizations and environments.
  • Rotate it immediately if it is exposed.
  • Persist the organization returned by the API next to the credential so records cannot cross tenants.

OAuth app

Use an OAuth app when other Wafeq customers connect their organizations to your product.

1. Create the app in the Developer Portal

Sign in or create a developer account in the Wafeq Developer Portal. A developer organization owns your apps and team members independently of any Wafeq accounting organization.

Create an app, then save its client ID and client secret. The client secret is shown only when it is issued. Store it in a server-side secret manager; if you lose it, rotate it in the portal.

Configure:

  • Redirect URIs: exact HTTPS callback URLs. HTTP is accepted only for localhost and 127.0.0.1 during local development. Fragments and wildcards are not accepted, and an app can have up to 10 redirect URIs.
  • Scopes: select only the read and write access your integration needs. A write scope also covers the corresponding read access.
  • App information: name, descriptions, logo, developer identity, website, privacy policy, and support details shown during consent.

The portal also lets your team inspect connected organizations and users, revoke a connection, rotate the client secret, and view connection analytics.

Rotating the client secret stops the old secret from being used for future token exchanges. Access tokens already granted by customers remain valid. Deleting the app revokes all of its issued tokens.

2. Test with a demo organization

From the app's Testing tab, seed a demo organization before connecting a real customer. The portal returns its access and refresh tokens once and fills it with representative invoices, bills, and expenses. Save the tokens before closing the dialog.

A demo connection uses the same API and authorization model as a customer connection.

3. Send the customer to Wafeq

Build an authorization URL on your server:

https://app.wafeq.com/oauth/authorize/
  ?response_type=code
  &client_id=<CLIENT_ID>
  &redirect_uri=https%3A%2F%2Fexample.com%2Fwafeq%2Fcallback
  &scope=contacts.read%20invoices.write
  &state=<UNPREDICTABLE_STATE>

The redirect_uri must exactly match one configured in the Developer Portal. Generate an unpredictable, single-use state, bind it to the initiating browser session, and reject the callback if it does not match.

A Wafeq organization owner or admin signs in, chooses one organization, reviews your app and requested scopes, and grants or denies access. Wafeq redirects to your callback with an authorization code and the original state.

4. Exchange the authorization code

Exchange the code from your server. Authenticate the client with HTTP Basic and send the same redirect URI:

curl --request POST "https://app.wafeq.com/oauth/token/" \
  --user "<CLIENT_ID>:<CLIENT_SECRET>" \
  --header "Content-Type: application/x-www-form-urlencoded" \
  --data-urlencode "grant_type=authorization_code" \
  --data-urlencode "code=<AUTHORIZATION_CODE>" \
  --data-urlencode "redirect_uri=https://example.com/wafeq/callback"

A successful response includes:

{
  "access_token": "<ACCESS_TOKEN>",
  "refresh_token": "<REFRESH_TOKEN>",
  "token_type": "Bearer",
  "expires_in": 2592000,
  "scope": "basic contacts.read invoices.read invoices.write",
  "organization_id": 12345
}

Treat organization_id as the tenant binding for this connection. Store it with the granted scopes, token expiry, access token, and refresh token. Do not infer the organization from a user-supplied value.

Authorization codes are short-lived and single-use. Never log them.

5. Refresh access

Refresh on your server before the access token expires:

curl --request POST "https://app.wafeq.com/oauth/token/" \
  --user "<CLIENT_ID>:<CLIENT_SECRET>" \
  --header "Content-Type: application/x-www-form-urlencoded" \
  --data-urlencode "grant_type=refresh_token" \
  --data-urlencode "refresh_token=<REFRESH_TOKEN>"

Wafeq rotates the refresh token. Replace the stored access token, refresh token, expiry, and granted scope atomically; do not keep using the old refresh token. Never expose a client secret or refresh token to browser or mobile code.

Make your first API request

Call Retrieve organization with either credential.

API key:

curl "https://api.wafeq.com/v1/organization/" \
  --header "Authorization: Api-Key <API_KEY>" \
  --header "Accept: application/json"

OAuth app:

curl "https://api.wafeq.com/v1/organization/" \
  --header "Authorization: Bearer <ACCESS_TOKEN>" \
  --header "Accept: application/json"

A successful 200 response confirms the credential and identifies the connected accounting organization. Persist that organization identity with the connection so records are never sent to the wrong tenant.

Retrieve accounting IDs

Accounting documents reference other Wafeq records by ID. A useful next request is List accounts:

curl "https://api.wafeq.com/v1/accounts/?page_size=100" \
  --header "Authorization: <Api-Key API_KEY or Bearer ACCESS_TOKEN>" \
  --header "Accept: application/json"

To find accounts that can receive or pay money, add is_payment_enabled=true.

You will commonly need IDs from:

For OAuth, the access token must include the scope required by the endpoint. The API reference lists the available scope names; select them on the app before asking a customer to authorize it.

Pagination

List endpoints return count, next, previous, and results. Continue requesting the URL in next until it is null. Do not assume that the first page contains every record.

Use page_size to control page size and page only when you need direct page navigation.

Common responses

StatusMeaningWhat to do
200 or 201Request succeededStore the returned Wafeq ID
400Request could not be validatedRead the field-level error and correct the payload
401Credential or access token is missing or invalidCheck the header; refresh OAuth access or rotate an exposed API key
403Access is not allowedFor OAuth, check the granted scope; otherwise check organization access and feature availability
404The object does not exist in this organizationVerify the ID and tenant mapping
429Too many requestsBack off and retry according to the response
5xxTemporary server failureRetry safely with backoff and idempotency

Before creating data

For POST requests that expose the header, send a unique X-Wafeq-Idempotency-Key. Reuse the same value only when retrying the same logical operation.

Next, choose an end-to-end workflow:


Did this page help you?