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.
https://tally.ac/api/public/partner/v1Version: v1 (stable)Auth: Bearer API keyFree to integrateHow 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.
- 1You set up your organisation once in the portalVerify your domain, define a "space template" (name, emoji, logo, default currency), mint an API key.
- 2You call POST /users for each customerPass 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.
- 3They open the magic-link email and see your brandThe space header carries your logo and a "Powered by Tally" footnote. No re-auth, no setup wizard.
- 4They upgrade — you earn 35% for lifeWe track the attribution automatically from the space they arrived through.
Quickstart
Provision your first user in five minutes
- 1Open the partner portal & create your accountHead to the portal, fill in your company details, and accept the partner agreement.
- 2Verify your domainAdd the TXT record we give you. This unlocks API keys and lets us show your brand in transactional emails.
- 3Create a space templateGive it a slug (e.g.
default), a name, an emoji, and a currency. This is the identifier you pass in the API. - 4Mint an API keyIt's shown once — store it in your secret manager immediately.
- 5Provision a test userRun the cURL below with your own email. You'll receive the magic-link welcome.
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.
| Field | Format | Notes |
|---|---|---|
| slug | lowercase, letters/numbers/-/_ | Stable identifier passed in the API. E.g. default, pro-plan, gym-owner. |
| name | ≤ 80 chars | Shown at the top of the user's space. |
| emoji | 1–4 chars | Fallback when no logo is set. |
| logo_url | https URL, ≤ 500 chars | Displayed in the space header and welcome email. Square PNG/SVG recommended. |
| currency | ISO 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
| Field | Type | Description |
|---|---|---|
| string (email) | The end-user's email. We create or match their Tally account by this address. | |
| external_user_id | string, ≤ 200 | Your stable id for them. Used as the idempotency key — repeat calls with the same value return the same space. |
| space_template | string (slug) | The template you defined in the portal. Omit to fall back to the seeded default template. |
Successful response (201)
{
"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
| Status | Body | Meaning |
|---|---|---|
| 400 | invalid_request | Missing/invalid body fields. |
| 401 | unauthorized | API key missing, malformed, or revoked. |
| 404 | unknown_template | space_template slug doesn't match an active template for your partner. |
| 429 | rate_limited | You're above 300 provision requests/minute per partner. |
| 500 | internal_error | Retry 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 -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.
| Status | When | Retry? |
|---|---|---|
| 400 | Body validation failed. | No — fix the payload. |
| 401 | Missing / invalid / revoked API key. | No — check credentials. |
| 403 | Domain unverified or partner suspended. | No — resolve in portal. |
| 404 | space_template slug doesn't exist. | No — create it in portal. |
| 409 | Idempotency conflict (very rare). | No — inspect state. |
| 429 | Rate limited. | Yes — respect Retry-After. |
| 5xx | Transient 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).
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 /usersnow acceptsspace_template(slug). Inlinespaceobject deprecated — supported through v1, removed in v2. Default template auto-seeded per partner so existing callers keep working. - 2026-07-10 ·
POST /documentsreleased. 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.
Legal