--- sidebar_position: 4 --- # Getting Credentials via OAuth ## Introduction If you're an **integrator** building a platform or service that needs to access noon Partner APIs on behalf of multiple sellers, you can use the **OAuth flow** to programmatically create service accounts in your integrator project and grant them access to seller projects. This eliminates the need for sellers to manually create and share service account credentials. This flow involves: - Your **integrator service account credentials**, which you use to authenticate your requests as the integrator, including the OAuth API endpoints described in this guide - Your **OAuth client credentials** (`client_id` and `client_secret`), which identify your OAuth application during the authorization code exchange After the seller grants consent and you complete the exchange, noon returns a **new service account credential in your integrator project** with access to the seller's project. Use that returned credential for ongoing API calls you make on that seller's behalf. :::info What is OAuth? OAuth 2.0 is an industry-standard authorization framework that enables applications to obtain delegated access to resources on behalf of a resource owner. If you're new to OAuth, we recommend reading the [OAuth 2.0 overview][oauth-spec] or the [official specification (RFC 6749)][oauth-rfc] to understand the basic concepts before implementing the flow. ::: The OAuth flow allows your application to: - Obtain authorization from sellers to access their noon Partner account - Automatically create a service account in **your integrator project** - Grant that service account access to the **seller's project** - Receive the service account credentials directly in the token exchange response ## Why Use OAuth? OAuth is ideal for integrators who: - **Manage multiple sellers** - Automate credential management for many sellers at scale - **Need centralized control** - Manage all service accounts from your own integrator project - **Want better security** - Sellers never need to manually create or share credentials with you - **Require programmatic access** - Trigger service account creation via API without manual intervention ## Prerequisites - You must be registered as a **noon Partner Integrator** - You already have **your own integrator service account credentials** and can authenticate requests as your integrator project. If you have not set this up yet, first follow [Getting Your Credentials][getting-credentials] and [Authenticating Your Requests][authenticating-requests]. - You have requested and obtained **OAuth client credentials** (client_id and client_secret) from noon - contact noon to request these credentials. Store the secret when it is issued. - Your sellers must have a noon Partner account :::warning OAuth API calls must be authenticated The OAuth API endpoints in this guide are **not anonymous endpoints**. Your backend must call them using an authenticated noon session created from **your integrator service account credentials**. The `client_id` and `client_secret` in the request body identify your OAuth application, but they do **not** replace the authenticated integrator session required to call noon APIs. Seller credentials are also not used here. Seller access is granted by the OAuth consent flow, and the seller-scoped service account credential is returned only after the token exchange completes. ::: ## How OAuth Flow Works The OAuth flow for creating service accounts follows these steps: ```mermaid sequenceDiagram participant Seller participant Integrator participant NoonAuth as noon OAuth participant NoonAPI as noon API Seller->>Integrator: Initiates connection Integrator->>Integrator: Generate code_verifier, derive code_challenge Integrator->>NoonAuth: Redirect to authorization URL (with code_challenge) NoonAuth->>Seller: Show consent screen Seller->>NoonAuth: Grants permission NoonAuth->>Integrator: Returns authorization code Integrator->>NoonAPI: Exchange code for access token (with code_verifier) NoonAPI->>Integrator: Returns access token (with seller project_code) Integrator->>NoonAPI: Exchange token to create service account NoonAPI->>Integrator: Returns credentials in result ``` ### Step 1: Authorization URL Direct sellers to the noon authorization URL to grant your application access: ``` https://oauth.noon.partners/? client_id=YOUR_CLIENT_ID &state=RANDOM_STATE_STRING &redirect_uri=YOUR_CALLBACK_URL &code_challenge=CODE_CHALLENGE &code_challenge_method=S256 ``` **Parameters:** - `client_id`: Your OAuth client ID provided by noon - `state`: A random string to prevent CSRF attacks - `redirect_uri`: Optional. Which of your registered callback URLs to return the seller to. See [Choosing a Callback URL](#choosing-a-callback-url) - `code_challenge`: The PKCE challenge derived from a per-flow `code_verifier` you keep server-side. Required if your OAuth client is configured to require PKCE — see [Securing the Flow with PKCE](#securing-the-flow-with-pkce) - `code_challenge_method`: Must be `S256`. Only SHA-256 is supported #### Choosing a Callback URL An OAuth client can register more than one callback URL — for example, separate URLs per environment. Name the one you want with `redirect_uri`: - **Omit it** and the seller is returned to the **first** URL registered on your client. - **Send it** and it must match one of your registered URLs exactly, including scheme, host, path and query. A mismatch is rejected rather than redirected, so the error stays on the consent screen instead of being sent to an unverified URL. Adding or changing a registered callback URL is a change to your OAuth client — contact Support. ### Step 2: Receive Authorization Code After the seller grants permission, they will be redirected to your registered callback URL with an authorization code: ``` https://your-app.com/callback?code=AUTH_CODE&state=RANDOM_STATE_STRING&iss=https%3A%2F%2Foauth.noon.partners ``` Alongside `code` and `state`, the callback carries an `iss` parameter naming the authorization server that issued the code — `https://oauth.noon.partners`. This is RFC 9207 issuer identification, and it lets a client that talks to more than one authorization server detect a code being replayed from a different one. If this is the only authorization server you integrate with, you can ignore it — but do not treat unexpected query parameters as an error: compare `iss` if you validate it, and ignore it otherwise. ### Step 3: Exchange Code for Access Token Use the authorization code to obtain an access token by calling the OAuth token endpoint: Call this endpoint from your backend using the authenticated integrator session described in [Authenticating Your Requests][authenticating-requests]. **Endpoint:** [`POST /identity/oauth/v1/token/create`][oauth-create-token-api] **Request:** ```json { "grant_type": "authorization_code", "code": "AUTH_CODE", "client_id": "YOUR_CLIENT_ID", "client_secret": "YOUR_CLIENT_SECRET", "code_verifier": "CODE_VERIFIER" } ``` Send `code_verifier` only when the authorization URL for this flow carried a `code_challenge`, and send the verifier that matches that exact challenge. See [Securing the Flow with PKCE](#securing-the-flow-with-pkce). **Response:** ```json { "access_token": "eyJhbGc...", "token_type": "TOKEN_TYPE_BEARER", "expires_in": "3600s", "scopes": ["access:grant"], "project_code": "PRJ12345" } ``` ### Step 4: Create Service Account and Receive Credentials Exchange the access token to create a service account and receive its credentials: Like the token creation call, this request must be sent through your authenticated integrator session. **Endpoint:** [`POST /identity/oauth/v1/token/exchange`][oauth-exchange-token-api] **Request:** ```json { "access_token": "eyJhbGc..." } ``` **Response:** ```json { "status": { "code": 0 }, "project_code": "PRJ12345", "oauth_request_id": "f47ac10b-58cc-4372-a567-0e02b2c3d479", "result": { "key_id": "key-abc123", "private_key": "-----BEGIN RSA PRIVATE KEY-----\nMIIEpAIBAAKCAQ...", "channel_identifier": "my-integrator-prj12345-abc@p99999.idp.noon.partners", "project_code": "PRJ12345", "type": "apijwt", "issued_at": "2026-04-20T12:00:00Z" } } ``` **Response Fields:** - `status`: Indicates whether the workflow executed successfully (`code: 0` means success) - `project_code`: The seller's project code associated with this exchange - `oauth_request_id`: A unique identifier for this OAuth exchange request — use this to track the request (see [Tracking OAuth Requests](#tracking-oauth-requests) below) - `result`: The service account credentials — contains the private key and metadata needed to authenticate API calls. The schema matches the credential object returned by the [API User Service CreateCredential][apiuser-create-credential-api] endpoint. The `channel_identifier` on this object is the same value the [API User Service][apiuser-managing-credentials] accepts in place of `user_code`, so you can rotate or revoke this credential without needing to look up a `user_code` separately. :::warning Store Your Credentials Securely The `result` contains the **private key**, which is only returned once — noon does not store it. If you lose it, you will need to create a new credential via the [API User Service][apiuser-managing-credentials]. Store it in a secrets manager, never commit it to source control, and never log it. ::: ## Securing the Flow with PKCE The authorization code is delivered to your callback through the seller's browser, so it passes through URL bars, browser history, referrer headers, and any proxy in between. PKCE (Proof Key for Code Exchange, [RFC 7636][pkce-rfc]) makes a leaked code useless on its own: the code becomes redeemable only by the application that started that specific flow. You generate a random `code_verifier` per flow and keep it on your backend. You send only its SHA-256 hash — the `code_challenge` — on the authorization URL. The authorization server binds that challenge to the authorization code it issues, and at Step 3 it checks that the `code_verifier` you present hashes to the same value. Because SHA-256 cannot be reversed, anyone who intercepted the challenge or the code still cannot produce the verifier. ### How to Implement It 1. **Before redirecting the seller**, generate a `code_verifier`: a cryptographically random string, 43-128 characters long, using only the characters `A-Z a-z 0-9 - . _ ~`. Generating 32 random bytes and base64url-encoding them without padding produces a valid 43-character verifier. 2. **Derive the `code_challenge`** as the base64url encoding, without padding, of the SHA-256 hash of the verifier. 3. **Store the verifier** on your backend against the same session record where you keep `state`. It must never reach the browser. 4. **Add `code_challenge` and `code_challenge_method=S256`** to the authorization URL in Step 1. 5. **Send the matching `code_verifier`** in the Step 3 token creation request. Use a fresh verifier for every authorization flow and never reuse one. ### PKCE Parameters | Parameter | Where | Description | |-----------|-------|-------------| | `code_challenge` | Authorization URL (Step 1) | Base64url, unpadded, of `SHA-256(code_verifier)` | | `code_challenge_method` | Authorization URL (Step 1) | Must be `S256`. `plain` is not supported | | `code_verifier` | Token creation body (Step 3) | The original random string, 43-128 characters from `A-Z a-z 0-9 - . _ ~` | ### When PKCE Is Required PKCE is enabled per OAuth client, and your client's configuration is the only thing that decides whether it is mandatory — removing `code_challenge` from the authorization URL does not opt a configured client out. | Your OAuth client | What is expected | |-------------------|------------------| | Configured to require PKCE | Every flow must carry a `code_challenge`, and every token creation call must carry the matching `code_verifier`. Requests without them are rejected | | Not configured to require PKCE | PKCE is optional per flow. If a flow sends a `code_challenge`, that flow must send the matching `code_verifier`. If it does not, the token creation call must omit `code_verifier` entirely | Existing integrations that predate PKCE keep working unchanged until the requirement is enabled on your client. :::tip Adopt PKCE before it is required Implement PKCE first, confirm your flows work end to end, then contact Support to have the requirement enabled on your OAuth client. PKCE is expected to become mandatory for all OAuth clients over time, so integrations that adopt it early will not need changes later. ::: ## Security Considerations :::warning Important - **Never expose your client_secret** in client-side code or public repositories - **Store your client_secret when it is issued**. If it is lost or you suspect it leaked, contact Support to have it rotated - **Validate the state parameter** to prevent CSRF attacks - **Use PKCE** so an intercepted authorization code cannot be redeemed without your `code_verifier` - **Keep the code_verifier on your backend** - it must never be sent to the browser or included in the authorization URL - **Store access tokens securely** and never log them - **Use HTTPS** for all OAuth communications - **Access tokens are single-use** - exchange them immediately for service account creation ::: ## Token Lifecycle - **Access Token Lifetime:** 1 hour (single use) - **Authorization Code Lifetime:** 10 minutes (single use) ## Error Handling Common error scenarios: | Error | Description | Solution | |-------|-------------|----------| | `Invalid or expired authorization code` | Authorization code has expired (10 min) or already been used | Request new authorization from seller | | `Invalid client_id` | OAuth client ID not found | Verify your client_id | | `redirect_uri does not match any registered redirect URI for this client` | The `redirect_uri` on the authorization URL is not one of your registered callback URLs | Send a registered URL exactly as registered, or omit the parameter to use the first one. See [Choosing a Callback URL](#choosing-a-callback-url) | | `Invalid client_secret` | Client secret does not match | Verify your client_secret | | `Invalid, expired, or already used access token` | Access token is invalid, expired, or has already been consumed | Restart the OAuth flow to get a new access token | | `code_verifier is required: the authorization request used PKCE` | The flow started with a `code_challenge`, but the token creation call did not send a `code_verifier` | Send the `code_verifier` you stored for this flow. See [Securing the Flow with PKCE](#securing-the-flow-with-pkce) | | `Invalid code_verifier` | The `code_verifier` does not hash to the `code_challenge` bound to this authorization code | Send the verifier belonging to *this* flow. A mismatch usually means verifiers were mixed up between concurrent flows or regenerated after the redirect. The code is consumed by the failed attempt, so restart from Step 1 | | `code_verifier provided but the authorization request did not use PKCE` | A `code_verifier` was sent for a flow whose authorization URL carried no `code_challenge` | Either send `code_challenge` on the authorization URL, or omit `code_verifier` from the token creation call. Do not mix the two | | `This client requires PKCE: the authorization request carried no code_challenge` | Your OAuth client requires PKCE, but this authorization code was issued for a flow without a `code_challenge` | Restart from Step 1 with `code_challenge` and `code_challenge_method=S256` on the authorization URL | | `code_verifier must be 43-128 characters long and use only [A-Za-z0-9-._~]` | The `code_verifier` does not meet the RFC 7636 format | Generate the verifier as described in [Securing the Flow with PKCE](#securing-the-flow-with-pkce) — base64url encoding without padding, no `+`, `/`, or `=` characters | | `User is not active` | The target service account is currently deactivated, so credentials cannot be generated | Reactivate the service account from the [API Users page][partner-api-access], then restart the OAuth flow from Step 1 to get a fresh authorization code and access token | | `apijwt active key quota exceeded for the account` | The service account behind this OAuth grant already has the maximum number of active keys (default: 5), so the token exchange cannot mint another one | Revoke an existing key on that service account, then restart the OAuth flow. See [Recovering from a Key Quota Exceeded Error](#recovering-from-a-key-quota-exceeded-error) below | | Non-successful response from token exchange | The ExchangeToken call returned a non-success status (e.g. timeout, or other unexpected failure) | Restart the OAuth flow from Step 1 to obtain a new authorization code and access token. Since access tokens are single-use, the original token cannot be retried | ### PKCE Errors on the Consent Screen Some PKCE problems are caught before any authorization code exists, so the seller sees them on the consent screen instead of your backend receiving an API error. No code is issued, and the seller cannot complete the flow until you fix the authorization URL. | Error | Cause | |-------|-------| | `This client requires PKCE: code_challenge is missing` | Your OAuth client requires PKCE, but the authorization URL had no `code_challenge` | | `code_challenge must be 43-128 characters long and use only [A-Za-z0-9-._~]` | The challenge is not base64url-encoded without padding, or is the wrong length | | `Unsupported code_challenge_method: plain. Supported: S256` | `code_challenge_method` was set to something other than `S256` | | `code_challenge_method requires code_challenge` | `code_challenge_method` was sent without a `code_challenge` | If a seller reports being unable to complete consent, check the authorization URL your application generated before investigating further. ### Recovering from a Key Quota Exceeded Error Each service account can have at most 5 active keys. Because the OAuth token exchange always tries to mint a new key on the seller's service account, repeatedly re-running OAuth for the same seller — for example, after losing a previously issued private key — will eventually hit `apijwt active key quota exceeded for the account`. The fix is to free up a key slot on that service account *before* re-running the exchange. Use the [API User Service][apiuser-managing-credentials] to do this. You don't need a `user_code` — the `channel_identifier` you received from a prior token exchange `result` works as the account identifier (see [Identifying the Target Service Account][apiuser-managing-credentials]). 1. **Pick what to revoke.** - If you still have a previously issued `key_id` that you no longer need, revoke just that key with [RemoveCredentials][apiuser-remove-credentials-api] and a specific `key_id`. - If you've lost track of all prior keys for this service account, call [RemoveCredentials][apiuser-remove-credentials-api] without a `key_id` to deactivate every active key on the account at once. Any application still authenticating with one of those keys will start failing immediately, so only do this when you are sure none of the existing keys are in use. 2. **Re-run the OAuth flow.** Because access tokens are single-use, you'll need a fresh authorization code from the seller and a new access token before retrying Step 4. 3. **Store the new credential.** Treat the new `result.private_key` as you would any other — it is only returned once. :::tip Prefer revoking a specific key when you can Removing all keys is a blunt instrument. If you have *any* working key for the account, revoke that specific `key_id` and let other live credentials (e.g. ones a seller-side automation is still using) keep working. ::: ## Code Examples See the [OAuth Quickstart Guide][oauth-quickstart] for complete code examples in multiple languages showing how to implement the OAuth flow. ## Tracking OAuth Requests Every token exchange returns an `oauth_request_id` that you can use to monitor the progress of the service account creation workflow. This is especially useful when managing OAuth integrations at scale across multiple sellers. To track request status: 1. Log in to the [noon Partners Access App][partner-access-app] 2. Navigate to the **OAuth** tab in your integrator project 3. You will see a list of all OAuth requests initiated for your OAuth clients in the past 24-48 hours. 4. Use the `oauth_request_id` to find and trace a specific request Each request progresses through the following statuses: | Status | Description | |--------|-------------| | `requested` | The seller initiated the OAuth authorization flow | | `granted` | The seller approved the consent screen | | `processing` | The authorization code was exchanged for an access token | | `executing` | The token was consumed and the service account creation workflow is running | | `completed` | The workflow finished successfully and the service account is ready | :::tip Store the `oauth_request_id` from the ExchangeToken response in your system. If a seller reports issues with their integration setup, you can use this ID to quickly look up the request status and diagnose any problems. ::: ## Next Steps - Review the [OAuth API Reference][oauth-api-reference] for detailed endpoint documentation - Check out the [OAuth Quickstart Guide][oauth-quickstart] for working code examples - Learn about [Managing Credentials via API][apiuser-managing-credentials] to rotate or revoke credentials after initial creation - Learn about [Authenticating Your Requests][authenticating-requests] using the credentials you receive