> ## Documentation Index
> Fetch the complete documentation index at: https://docs.tangoanalytics.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Authentication

> Request, store, and use Tango integration credentials &mdash; API keys for REST endpoints, user/password (or SSH key) for SFTP.

Every Tango integration requires credentials issued by your Tango onboarding lead. There are no self-service signup flows — credentials are provisioned per client per environment.

This page covers how to request credentials, where to use them in REST and SFTP calls, how to keep them safe, and what each common authentication error means.

<CardGroup cols={2}>
  <Card title="REST API auth" icon="code" href="#rest-api-authentication">
    Two headers on every request — `x-client` plus `x-api-key`. Applies to all `/v1/...` endpoints.
  </Card>

  <Card title="SFTP auth" icon="server" href="#sftp-authentication">
    Username + password (or SSH public key) per environment. Applies to all file-based integrations.
  </Card>
</CardGroup>

## Requesting credentials

<Steps>
  <Step title="Contact your Tango onboarding lead">
    Send an email naming your integrating system, the integrations you plan to use (e.g. "AP Inbound + AR Outbound"), and which environments you need (Test always; Production once UAT signs off).

    Include the **system contact** (a real person who will receive expiry / rotation notifications), not a shared inbox.
  </Step>

  <Step title="Receive credentials separately per environment">
    You'll get two pairs — one for Test, one for Production — usually delivered through your tenant's secrets-sharing channel (1Password, Bitwarden, an encrypted email attachment, etc.):

    * **REST APIs:** an `x-client` identifier and an `x-api-key` secret.
    * **SFTP:** an SFTP username and password (or instructions to register a public SSH key).

    <Warning>
      Production credentials are issued only after Test integrations have been signed off. Don't request both at once; you'll be asked to demonstrate Test runs first.
    </Warning>
  </Step>

  <Step title="Store credentials in a secret manager">
    Before you use them anywhere, put each value in a secret manager (AWS Secrets Manager, HashiCorp Vault, Azure Key Vault, 1Password Secrets Automation, etc.). Reference the secret by name in your application config rather than embedding it.

    Never commit credentials to a Git repository, even in private repos — rotating after an accidental commit is painful and creates a window where the leaked value is still valid.
  </Step>

  <Step title="Verify with a noop call">
    Confirm everything works before you wire it into your real pipeline. Hit the status endpoint with a known-bad `gu_id` — you'll get a clean error rather than an auth failure:

    ```bash theme={null}
    curl "https://testapi.tangoanalytics.com/v1/batch/status?gu_id=00000000-0000-0000-0000-000000000000" \
      -H "x-client: $TANGO_CLIENT" \
      -H "x-api-key: $TANGO_API_KEY"
    ```

    A response with `"status": "ERROR"` and `"error": "Invalid gu_id"` (or similar) means auth is working. A 401 / 403 means it isn't — jump to the [troubleshooting](#troubleshooting) section.
  </Step>
</Steps>

## REST API authentication

Every call to `api.tangoanalytics.com` (or `testapi.tangoanalytics.com`) requires two headers:

| Header | Purpose | Example |
| - | - | - |
| `x-client` | Your Tango-issued client identifier. Identifies *which* client is calling. | `acme-corp` |
| `x-api-key` | Your Tango-issued API key. Authorises the call. | `tk_live_3f2a...` |

Both are required on every request — including the polled `/v1/batch/status` endpoint. Submission endpoints (PUT/POST/GET) and status endpoints share the same credentials.

<Info>
  `x-client` is **not** a secret — it's an identifier and may appear in logs. `x-api-key` **is** a secret and must be treated like a password: secret-managed, rotated, and never logged in plaintext.
</Info>

### Example: send an authenticated request

<CodeGroup>
  ```bash curl theme={null}
  curl -X PUT "https://testapi.tangoanalytics.com/v1/suppliermgmt/batch/agreements" \
    -H "x-client: $TANGO_CLIENT" \
    -H "x-api-key: $TANGO_API_KEY" \
    -H "Content-Type: application/json" \
    -d '{ "records": [ { "contract_number": "...", "...": "..." } ] }'
  ```

  ```python python theme={null}
  import os, requests

  response = requests.put(
      "https://testapi.tangoanalytics.com/v1/suppliermgmt/batch/agreements",
      headers={
          "x-client":     os.environ["TANGO_CLIENT"],
          "x-api-key":    os.environ["TANGO_API_KEY"],
          "Content-Type": "application/json",
      },
      json={"records": [{"contract_number": "...", "...": "..."}]},
      timeout=30,
  )
  response.raise_for_status()
  ```

  ```javascript node.js theme={null}
  import fetch from "node-fetch";

  const response = await fetch(
    "https://testapi.tangoanalytics.com/v1/suppliermgmt/batch/agreements",
    {
      method: "PUT",
      headers: {
        "x-client":     process.env.TANGO_CLIENT,
        "x-api-key":    process.env.TANGO_API_KEY,
        "Content-Type": "application/json",
      },
      body: JSON.stringify({ records: [/* ... */] }),
    },
  );
  ```

  ```java java theme={null}
  import java.net.URI;
  import java.net.http.*;

  HttpClient client = HttpClient.newHttpClient();
  HttpRequest request = HttpRequest.newBuilder()
      .uri(URI.create("https://testapi.tangoanalytics.com/v1/suppliermgmt/batch/agreements"))
      .header("x-client",     System.getenv("TANGO_CLIENT"))
      .header("x-api-key",    System.getenv("TANGO_API_KEY"))
      .header("Content-Type", "application/json")
      .PUT(HttpRequest.BodyPublishers.ofString("{\"records\":[/* ... */]}"))
      .build();

  HttpResponse<String> response = client.send(request, HttpResponse.BodyHandlers.ofString());
  ```
</CodeGroup>

### Auth scheme: API key, not OAuth

Tango uses static API keys delivered in headers — there is no token-exchange / refresh flow. If you're used to OAuth 2.0 client credentials, the simplification is:

* No `/oauth/token` round-trip on each session.
* No access-token expiry to handle in code.
* Rotation is human-driven (you request a new key, Tango issues it, you swap and decommission).

The tradeoff: a leaked key is valid until rotation. Treat the key as the highest-sensitivity secret in your integration's config.

## SFTP authentication

File-based integrations (AP/AR/GL/PO/CO/Sales file variants) authenticate against an SFTP host:

| Environment | Host |
| - | - |
| Production | `sftp.tangoanalytics.com` |
| Test (SIT/UAT) | `sftpdev.tangoanalytics.com` |

Two auth modes are supported:

<Tabs>
  <Tab title="Username + password">
    The simplest setup — Tango provisions a per-client username and password. Use it the same way as any SFTP login:

    ```bash theme={null}
    # Interactive
    sftp tango-client@sftpdev.tangoanalytics.com

    # Scripted (with sshpass, for non-interactive batch jobs)
    sshpass -e sftp -oBatchMode=no -b commands.txt tango-client@sftpdev.tangoanalytics.com
    ```

    <Warning>
      Password auth requires you to handle the password securely — use a secret manager, not a hard-coded value or a plaintext config file. Prefer SSH-key auth if your environment supports it.
    </Warning>
  </Tab>

  <Tab title="SSH public key">
    Recommended for production. Generate a key pair on the machine that will run the integration, register the **public** key with Tango onboarding, and keep the **private** key in a secret manager or restricted file (`chmod 600`):

    ```bash theme={null}
    # 1. Generate a key pair (one time, on the host that will run the integration)
    ssh-keygen -t ed25519 -f ~/.ssh/tango_sftp -C "tango-sftp-prod"

    # 2. Send the .pub file to your Tango onboarding lead.
    cat ~/.ssh/tango_sftp.pub

    # 3. Once registered, connect using the private key
    sftp -i ~/.ssh/tango_sftp tango-client@sftpdev.tangoanalytics.com
    ```

    <Tip>
      Use a **separate key pair per environment** (one for Test, one for Production). Rotate them independently and never re-use a non-prod key in prod.
    </Tip>
  </Tab>
</Tabs>

### Path-level access

Your SFTP user is scoped to a specific directory tree. You'll typically see:

```text theme={null}
/inbound/<integration>/   # files you upload to Tango (AP, AR, Sales, etc.)
/outbound/<integration>/  # files Tango writes for you to pull (AP, AR, GL, PO, CO, etc.)
/<dir>/Processed/         # files Tango has already consumed (read-only history)
```

Attempting to write outside `/inbound/...` or read outside `/outbound/...` will fail. If you need access to additional paths (for example, a new integration), request it through your onboarding lead.

## Environment separation

<Note>
  **Test credentials and Production credentials are independent.** Re-using one in the other will fail authentication, even if the values look similar — they're issued from different key pools.
</Note>

The recommended setup, per-environment:

| | Test | Production |
| - | - | - |
| REST base URL | `https://testapi.tangoanalytics.com` | `https://api.tangoanalytics.com` |
| SFTP host | `sftpdev.tangoanalytics.com` | `sftp.tangoanalytics.com` |
| Secret-manager keys | `TANGO_TEST_CLIENT`, `TANGO_TEST_API_KEY`, `TANGO_TEST_SFTP_*` | `TANGO_PROD_CLIENT`, `TANGO_PROD_API_KEY`, `TANGO_PROD_SFTP_*` |
| Allowed callers | Dev / CI / QA hosts | Prod hosts only |

Bind each set of credentials to a distinct secret-manager path so a misconfigured deployment can't accidentally pull prod credentials in a test environment.

## Security best practices

<AccordionGroup>
  <Accordion title="Never commit credentials" icon="git-branch">
    Add `.env`, `*.pem`, `id_rsa`, `tango_sftp*`, and any other credential-bearing files to `.gitignore`. If you accidentally commit a credential:

    1. **Rotate immediately** — the value is compromised the moment it's pushed, even to a private repo.
    2. Inform Tango via the security contact.
    3. Use `git filter-repo` or similar to scrub history — though rotation is the only thing that actually mitigates the leak.
  </Accordion>

  <Accordion title="Rotate keys on a schedule" icon="refresh-cw">
    Set a reminder to rotate API keys every 6–12 months, and immediately whenever:

    * An employee with access leaves your organisation.
    * You suspect a leak (logs published, repo briefly public, laptop lost).
    * A vulnerability scan flags the secret as exposed.

    To rotate: request a new key from Tango onboarding, deploy it everywhere, then ask Tango to revoke the old one. Avoid revoking the old key first — that breaks anything still using it.
  </Accordion>

  <Accordion title="Don't log credentials" icon="file-text">
    Configure your HTTP client and logging library to **redact** the `x-api-key` header in all logs (access logs, error logs, structured-event logs, distributed-trace spans). Most frameworks have header-redaction primitives; use them.

    Common gotchas:

    * `curl -v` prints headers including `x-api-key`. Don't paste verbose output into tickets or chat.
    * Some libraries log full request objects on retries / errors. Verify your retry middleware doesn't print the key.
  </Accordion>

  <Accordion title="Use short-lived environment variables, not config files" icon="terminal">
    For CI / cron / container jobs, pass credentials in as environment variables sourced from your secret manager at start time. Avoid writing them to disk; if you must, restrict permissions (`chmod 600`) and clean up afterward.
  </Accordion>

  <Accordion title="Limit access by IP if possible" icon="shield">
    If your Tango tenant supports IP allowlisting (ask onboarding), restrict your API key to the egress IPs of your integration hosts. This is a defence-in-depth layer — a leaked key with allowlist enforcement is harder to abuse.
  </Accordion>
</AccordionGroup>

## Troubleshooting

<AccordionGroup>
  <Accordion title="HTTP 401 Unauthorized" icon="lock">
    Your `x-api-key` header is missing, mistyped, or invalid. Check:

    * The header name is **exactly** `x-api-key` (lowercase, hyphenated). Some HTTP clients normalise to `X-Api-Key`, which is fine, but a typo like `xapikey` is not.
    * The value is not URL-encoded or quoted — it's a raw string.
    * You're pointing at the right environment (test key against test URL, prod key against prod URL).
    * The key hasn't been rotated; if it has, your secret manager may still hold the old value.
  </Accordion>

  <Accordion title="HTTP 403 Forbidden" icon="ban">
    Credentials are valid but your client doesn't have permission for this endpoint. Possible causes:

    * You're calling an integration your client hasn't been enabled for — e.g. trying AP endpoints when only AR was provisioned.
    * You're hitting Production with Test-scope credentials (or vice versa) and the API enforces strict separation.
    * IP allowlisting (if enabled) is rejecting your egress IP.

    Contact Tango with your `x-client` value and the endpoint you're calling.
  </Accordion>

  <Accordion title="HTTP 400 with \`status\_message\` about headers" icon="triangle-alert">
    A required header is missing or malformed. Often this is `Content-Type` rather than auth — submission endpoints require `Content-Type: application/json` for JSON bodies.
  </Accordion>

  <Accordion title="SFTP: \`Permission denied (publickey,password)\`" icon="server-off">
    SSH-key auth: the public key you registered isn't matching the private key the client is presenting. Check `ssh -i <keyfile> -v ...` output for which key the client tried, and confirm with Tango that they registered the corresponding `.pub`.

    Password auth: the password is wrong or the username is mistyped. Some clients silently strip whitespace; copy/paste from your secret manager carefully.
  </Accordion>

  <Accordion title="SFTP: \`No such file or directory\` writing to /inbound/<x>" icon="folder-x">
    Your SFTP user isn't scoped for that integration's directory. Ask Tango onboarding to grant write access to the relevant `/inbound/<integration>/` path.
  </Accordion>
</AccordionGroup>

## Credential lifecycle

<Steps>
  <Step title="Provision">
    Tango onboarding creates a client identifier and an API key (and SFTP user, if applicable), per environment.
  </Step>

  <Step title="Deploy">
    You load the credentials into your secret manager and deploy your integration. Verify with a noop call.
  </Step>

  <Step title="Operate">
    Run your integration. Don't touch the credentials in source code — pull them from the secret manager at runtime.
  </Step>

  <Step title="Rotate">
    Periodically (every 6–12 months) or on incident, request a new key. Deploy the new key everywhere, verify, then ask Tango to revoke the old one.
  </Step>

  <Step title="Decommission">
    When an integration is retired, ask Tango to revoke the associated credentials so they can't be reused. Delete the secret-manager entries.
  </Step>
</Steps>

## Next steps

<CardGroup cols={2}>
  <Card title="Quickstart" icon="rocket" href="/quickstart">
    Use the credentials you just set up to send your first request.
  </Card>

  <Card title="Overview" icon="compass" href="/overview">
    Background on Tango's integration patterns — REST async, SFTP, and the shared `gu_id` polling flow.
  </Card>
</CardGroup>
