For platforms, marketplaces & communities

One API call opens a Tally space for your customer.

Tally is a simple spend/earn tracker for the self-employed. As a partner, you turn every one of your users into a fully-branded Tally account with a single request — they receive a magic-link email from you, land on a space that carries your name and logo, and start tracking. You earn 35% for life when they upgrade to Pro.

Base URL: https://tally.ac/api/public/partner/v1Version: v1 (stable)Auth: Bearer API keyFree to integrate

How it works

You bring the users. We handle the accounting UI.

Your platform already knows who your customers are. Tally is the bookkeeping layer they'd otherwise ask you to build. Instead of cramming spend/earn views into your dashboard, you hand each customer a branded Tally space — under your name — and we run the numbers surface, mobile app, exports and receipts.

  1. 1
    You set up your organisation once in the portal
    Verify your domain, define a "space template" (name, emoji, logo, default currency), mint an API key.
  2. 2
    You call POST /users for each customer
    Pass their email, your stable id for them, and the template slug. We create or find their Tally user and provision a branded space in about 400ms.
  3. 3
    They open the magic-link email and see your brand
    The space header carries your logo and a "Powered by Tally" footnote. No re-auth, no setup wizard.
  4. 4
    They upgrade — you earn 35% for life
    We track the attribution automatically from the space they arrived through.

Revenue share

35% for life on every Pro subscription

The first partner attributed to a user keeps a 35% share of net revenue for as long as that user pays. Attribution is set on their first provisioning call and never re-assigned. Payouts run monthly in GBP via bank transfer once your pending balance clears £50.

There is no fee to integrate, no minimum volume, and no per-provision cost. Free-plan users cost you nothing.

Quickstart

Provision your first user in five minutes

  1. 1
    Open the partner portal & create your account
    Head to the portal, fill in your company details, and accept the partner agreement.
  2. 2
    Verify your domain
    Add the TXT record we give you. This unlocks API keys and lets us show your brand in transactional emails.
  3. 3
    Create a space template
    Give it a slug (e.g. default), a name, an emoji, and a currency. This is the identifier you pass in the API.
  4. 4
    Mint an API key
    It's shown once — store it in your secret manager immediately.
  5. 5
    Provision a test user
    Run the cURL below with your own email. You'll receive the magic-link welcome.
curl
curl -X POST https://tally.ac/api/public/partner/v1/users \
  -H 'Authorization: Bearer tp_<YOUR_API_KEY>' \
  -H 'Content-Type: application/json' \
  -d '{
    "email": "you@example.com",
    "external_user_id": "cust_test_1",
    "space_template": "default"
  }'

You'll get back user_id, space_id, connection_id, and (only on first mint) a connection_secret you can use later to POST entries or documents on their behalf.

Portal · Step 1

Create your partner account

Sign in with the email that owns your company. You'll accept the Partner Agreement, Data Processing Addendum and Acceptable Use Policy on this step. Only one partner account per legal entity — extra teammates can be added later.

Portal · Step 2

Verify your domain

We ask you to prove control of the domain you'll market Tally from. Add the TXT record we display, click Check DNS, and you're verified within a minute. Verification unlocks API keys, marketplace listing and the ability to appear as "Powered by Tally, brought to you by you" in transactional emails.

Name:  _tally-verify.yourbrand.com
Type:  TXT
Value: tally-verify-<random-32-hex>

Portal · Step 3

Design a space template

A space template is the branded preset applied every time you provision a new user. Instead of sending branding fields on every API call, you define them once in the portal and reference a slug. If you have multiple tiers (Free, Pro, Enterprise) or brands, create a template per variant.

FieldFormatNotes
sluglowercase, letters/numbers/-/_Stable identifier passed in the API. E.g. default, pro-plan, gym-owner.
name≤ 80 charsShown at the top of the user's space.
emoji1–4 charsFallback when no logo is set.
logo_urlhttps URL, ≤ 500 charsDisplayed in the space header and welcome email. Square PNG/SVG recommended.
currencyISO 4217 (GBP, USD, EUR…)The user can change this in-app if they want.

Editing a template updates future provisioning only. Existing user spaces are theirs — we do not push retroactive rebrands into their app.

Portal · Step 4

Generate an API key

API keys are prefixed tp_, 64 hex characters, and stored one-way hashed. The full key is shown once at creation and never again — put it in your secret manager immediately. You can revoke a key at any time without affecting existing user spaces or connections.

Best practice: one key per environment (production, staging), and rotate at least annually. Compromised keys can be revoked and re-minted in under 30 seconds.

Provisioning · Reference

POST /users — the only endpoint you strictly need

Given an email + your stable identifier + a template slug, we ensure a Tally user exists, provision a branded space owned by them, and return the ids you'll need if you later want to push entries or documents.

Request body

FieldTypeDescription
emailstring (email)The end-user's email. We create or match their Tally account by this address.
external_user_idstring, ≤ 200Your stable id for them. Used as the idempotency key — repeat calls with the same value return the same space.
space_templatestring (slug)The template you defined in the portal. Omit to fall back to the seeded default template.

Successful response (201)

json
{
  "user_id": "d34a…",
  "user_was_new": true,
  "space_id": "9f2c…",
  "space_was_new": true,
  "space_template": "default",
  "connection_id": "b12e…",
  "connection_secret": "tly_live_… (only on first mint — store it)"
}

Error responses

StatusBodyMeaning
400invalid_requestMissing/invalid body fields.
401unauthorizedAPI key missing, malformed, or revoked.
404unknown_templatespace_template slug doesn't match an active template for your partner.
429rate_limitedYou're above 300 provision requests/minute per partner.
500internal_errorRetry after a short backoff; contact support if persistent.

User experience

What your customer actually sees

A first-time Tally user receives a magic-link welcome email from us that names your company as the source. Clicking through drops them straight into the space with no signup form — Tally's own onboarding is skipped because you've vouched for the email.

An existing Tally user gets a lighter "your friends at you just set up a new space for you" email that deep-links into the new space. Their existing spaces are untouched.

Every space provisioned through your API carries a "Brought to you by you" chip in the space header, linking back to your website (subject to domain verification).

Welcome emails are sent asynchronously. POST /users returns as soon as the space is provisioned; the welcome email is queued and typically arrives within a minute. During large bursts (thousands of provisions in a short window) delivery is paced to match our email provider's rate limit so nothing gets dropped — magic links are minted at send time, not enqueue time, so their expiry clock starts when the email actually goes out. You can inspect per-user delivery status in the partner portal under Welcome emails.

Idempotency

Re-provisioning is safe

POST /users is idempotent on (partner_id, external_user_id). Call it every time your customer signs in if that's simpler than tracking lifecycle state on your side — we'll return the same space_id and connection_id and won't spam them with duplicate welcome emails.

The connection_secret field is only populated on the first call. If you lose it, revoke and re-provision from the portal — we cannot recover it because we only store the hash.

Branding updates

Change branding in one place

Your brand lives on the template, not on the API call. When you update a template's name, logo or currency in the portal,future provisioned spaces inherit the change automatically. Existing spaces stay as they were provisioned — the space belongs to the user now, and we don't rewrite user-facing content behind their back.

If you need a hard rebrand for an existing cohort, contact support and we'll walk through a bulk migration together.

Optional

POST /entries — push spends and earns

You can enrich the user's ledger with data your platform already has: payouts, order totals, subscription charges, refunds. This is optional — plenty of partners just provision the space and let the user enter their own numbers. When you do use it, entries are signed and idempotent so partial failures don't create duplicates.

curl
curl -X POST https://tally.ac/api/public/ingest/v1/entries \
  -H 'Authorization: Bearer <connection_secret>' \
  -H 'X-Connection-Id: <connection_id>' \
  -H 'Content-Type: application/json' \
  -d '{
    "kind": "earn",
    "amount_cents": 4200,
    "occurred_on": "2026-07-09",
    "note": "July payout",
    "external_id": "acme:payout:2026-07:abc123"
  }'

Use the connection_secret returned by POST /users. Full field reference is in the connection SDK and mirrored inline in the response schemas.

Optional

POST /documents — attach receipts, contracts, tax forms

Free on every plan. Push files up to 10 MB —multipart/form-data with a file, title, optional kind (receipt, invoice, statement, contract, tax, other), and an external_id for deduplication. The user sees them in the Documents tab of their space and can search, filter and export at tax time.

PDFs, JPGs, PNGs and HEIC are all accepted. Do not upload files that contain third-party PII you have no lawful basis to share (see the DPA).

Lifecycle

Revoking a connection

When a customer leaves your platform, revoke the connection from the portal or via the connection API. This stops future /entries and /documents writes but keeps the user's data inside their Tally space — the account is theirs. They can continue using it standalone or re-connect via another partner.

Revoking a connection does not revoke your revenue-share attribution. Once earned, always earned.

Lifecycle

Handling churned users

We recommend keeping the connection active and simply stopping your /entries writes when a user churns — the user might reactivate on your platform, or you might launch a re-engagement flow. Revoking is a hard break; leaving the connection dormant costs nothing.

Reference

Authentication headers

Every request to /api/public/partner/v1/* requires your partner API key in the Authorization header. Never send the key from a browser — it is a server-to-server credential. Requests over HTTP (not HTTPS) are rejected.

Authorization: Bearer tp_<64 hex chars>
Content-Type:  application/json

Reference

Errors & status codes

Errors are always JSON of the shape { "error": "human message", "detail": "machine hint" }. Machine-readable code field is added on request in v1 and standard in v2.

StatusWhenRetry?
400Body validation failed.No — fix the payload.
401Missing / invalid / revoked API key.No — check credentials.
403Domain unverified or partner suspended.No — resolve in portal.
404space_template slug doesn't exist.No — create it in portal.
409Idempotency conflict (very rare).No — inspect state.
429Rate limited.Yes — respect Retry-After.
5xxTransient server issue.Yes — exponential backoff, jitter, cap at 5 attempts.

Reference

Rate limits

Provisioning is limited to 300 requests/minute per partner, per rolling 60-second window. Entry writes (per connection) are 600/min and document uploads 60/min. Above the limit you get a 429 with a Retry-After header in seconds.

If your onboarding is bursty (e.g. daily batch of new signups), stagger the calls or contact support for a bespoke limit — we're happy to raise it once we know you.

Preview

Webhooks from Tally

Outbound webhooks are on the roadmap for Q4 2026. Signed with HMAC-SHA256 over the raw body, delivered with exponential backoff. Planned events: user.upgraded, user.downgraded, connection.revoked, space.archived. Register your intent in the portal and we'll email you when the preview opens.

SDK

TypeScript SDK

Zero-dependency, runs anywhere with global fetch (Node 18+, Bun, Deno, Cloudflare Workers, modern browsers). Two clients: TallyPartnerClient (partner API key, for provisioning) and TallyClient (connection secret, for pushing entries and documents).

typescript
import { TallyPartnerClient } from "@tally/sdk";

const partner = new TallyPartnerClient({
  apiKey: process.env.TALLY_PARTNER_KEY!,
});

const { space_id, connection_id, connection_secret } =
  await partner.provisionUser({
    email: user.email,
    external_user_id: user.id,
    space_template: "default",
  });

// Store connection_id + connection_secret if you'll push entries later.

Recipes

cURL cookbook

Provision a user

curl -X POST https://tally.ac/api/public/partner/v1/users \
  -H 'Authorization: Bearer tp_...' \
  -H 'Content-Type: application/json' \
  -d '{"email":"user@example.com","external_user_id":"cust_42","space_template":"default"}'

Push an entry (uses connection secret)

curl -X POST https://tally.ac/api/public/ingest/v1/entries \
  -H 'Authorization: Bearer <connection_secret>' \
  -H 'X-Connection-Id: <connection_id>' \
  -H 'Content-Type: application/json' \
  -d '{"kind":"earn","amount_cents":4200,"occurred_on":"2026-07-09","external_id":"payout_2026_07"}'

Upload a document

curl -X POST https://tally.ac/api/public/ingest/v1/documents \
  -H 'Authorization: Bearer <connection_secret>' \
  -H 'X-Connection-Id: <connection_id>' \
  -F 'file=@receipt.pdf' \
  -F 'title=July receipt' \
  -F 'kind=receipt' \
  -F 'external_id=receipt:2026-07:0001'

Operations

Versioning

The API is versioned in the URL (/v1/). Breaking changes ship as /v2/ alongside a minimum 6-month deprecation window for v1 — we email every partner on the announcement, at the halfway mark, and one month before shutdown.

Additive changes (new optional fields, new endpoints) can land in v1 without a version bump. Renamed or removed fields never do.

Operations

Changelog

  • 2026-07-10 · Space templates moved to the portal. POST /users now accepts space_template (slug). Inline space object deprecated — supported through v1, removed in v2. Default template auto-seeded per partner so existing callers keep working.
  • 2026-07-10 · POST /documents released. Free on every plan.
  • 2026-06-01 · v1 general availability. POST /users, POST /entries, GET /connection, GET /ping.

Operations

Status & support

We commit to 99.5% best-effort uptime on the partner endpoints. No SLA credit at this tier — see the Partner Agreement for the full commitment. Incidents are posted at status.tally.ac.

For anything else, email partners@tally.ac. Include your partner slug and, if relevant, a request id from a failing response. We reply within one business day.

Tally Partner API · v1Base URL: https://tally.ac/api/public/partner/v1