Firmify Public API — Developer Documentation
Overview
The Firmify Public API enables programmatic document generation, registry workflows, procurement doc-packs, and accounting integration. Build custom workflows and integrate Firmify into your existing systems.
Base URLs
https://app.firmify.bg/api/v1
https://app.firmify.bg/v1 # mirror (Next.js proxy)
Current version: v1. Authenticated responses include X-Api-Version: 2026-07.
Two access surfaces
| Surface | Auth | Use for |
|---|---|---|
/api/v1/* |
Bearer fmfy_... |
Programmatic document generation, billing, webhooks |
/developer/* |
Session cookie (JWT) | Developer portal UI — manage keys, billing, webhooks |
Interactive docs: Scalar · Swagger · Redoc · Changelog
Quick Start
- Create an API key in the Developer Portal (or
POST /api/v1/registerfor headless signup). - Authenticate with
Authorization: Bearer fmfy_.... - Generate a document — step by step:
# 1. Create an application (DRAFT status)
curl -X POST https://app.firmify.bg/api/v1/legal-docs \
-H "Authorization: Bearer fmfy_YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{"docType": "GDPR_POLICY"}'
# 2. Fill in company data
curl -X PUT https://app.firmify.bg/api/v1/legal-docs/{id} \
-H "Authorization: Bearer fmfy_YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{"companyName": "Acme OOD", "companyEik": "123456789"}'
# 3. Pay via Stripe checkout
curl -X POST https://app.firmify.bg/api/v1/legal-docs/{id}/checkout \
-H "Authorization: Bearer fmfy_YOUR_API_KEY"
# 4. Generate the document
curl -X POST https://app.firmify.bg/api/v1/legal-docs/{id}/generate \
-H "Authorization: Bearer fmfy_YOUR_API_KEY"
# 5. Download the PDF
curl https://app.firmify.bg/api/v1/legal-docs/{id}/download \
-H "Authorization: Bearer fmfy_YOUR_API_KEY" \
-o document.pdf
See the step-by-step tutorial for webhooks, sandbox mode, and async jobs.
Authentication
All authenticated /api/v1/* requests require a Bearer token:
curl -H "Authorization: Bearer fmfy_YOUR_API_KEY" \
https://app.firmify.bg/api/v1/pricing
API key format
Keys are prefixed with fmfy_ and shown only once at creation. Store them securely.
Scopes (permissions)
| Permission | Description |
|---|---|
public_api:v1 |
Full public API v1 access (granted by default on new keys) |
documents:generate |
Legacy alias for document generation (also accepted) |
accounting:read |
Read-only accounting data for one company |
accounting:multi |
Accounting data for all companies you can access |
The accounting API lives at /accounting/api/* (not under /api/v1). Use X-Company-Id or ?companyId= to target a company.
Sandbox keys
Create a sandbox key in the Developer Portal to test integrations without real Stripe charges or document generation. Sandbox batch operations return mock IDs.
Document Generation
Lifecycle: DRAFT → READY → PAID → GENERATED.
Legal documents
Path: /api/v1/legal-docs
Types: GDPR_POLICY, TERMS_OF_SERVICE, COOKIE_POLICY, NDA, SERVICE_CONTRACT, PRIVACY_NOTICE, DATA_PROCESSING_AGREEMENT
curl -X POST https://app.firmify.bg/api/v1/legal-docs \
-H "Authorization: Bearer fmfy_YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{"docType": "GDPR_POLICY"}'
Then PUT /{id} → checkout/charge/quota → POST /{id}/generate → GET /{id}/download.
HR documents
Path: /api/v1/hr-docs
Types: EMPLOYMENT_CONTRACT, EMPLOYMENT_CONTRACT_REMOTE, NDA_EMPLOYEE, AMENDMENT_CONTRACT, TERMINATION_AGREEMENT, JOB_DESCRIPTION, PROBATION_NOTICE, LEAVE_REQUEST, PERFORMANCE_WARNING
Same CRUD + checkout + generate + download pattern as legal docs.
Other categories
| Category | Path |
|---|---|
| Business docs | /api/v1/business-docs |
| VAT registration | /api/v1/vat |
| BULSTAT | /api/v1/bulstat |
| Bank onboarding | /api/v1/bank |
| Amendments | /api/v1/amendments |
| Share transfers | /api/v1/share-transfers |
| Dissolution | /api/v1/dissolution |
| Company registration | /api/v1/company-reg/{orgId} |
| Procurement | /api/v1/procurement |
Discover all products, JSON schemas, and credit costs via GET /api/v1/catalog.
Payment
Option A: Stripe Checkout (end-user pays)
curl -X POST https://app.firmify.bg/api/v1/legal-docs/{id}/checkout \
-H "Authorization: Bearer fmfy_YOUR_API_KEY"
Redirect the user to the returned url. After payment, status becomes PAID — call /generate.
Every category has a /checkout endpoint.
Option B: Direct charge (automated)
POST /api/v1/billing/setup→clientSecretfor Stripe.js- Collect card on your frontend
POST /api/v1/{category}/{id}/charge→ statusPAID→ call/generate
Or use monthly quota / credits — see Billing below.
Billing
Hybrid billing resolves payment in this order:
- API Pro — user-level subscription (€29/mo, 30 docs/month shared across keys)
- Key tier quota — per-key monthly doc limit (free/starter/pro/enterprise)
- Credit wallet — pre-paid credits (category-weighted costs)
- Stripe Checkout / charge — per-document payment
curl -H "Authorization: Bearer fmfy_YOUR_API_KEY" \
https://app.firmify.bg/api/v1/billing
When payment is required and no quota/credits remain, generation returns 402 with checkoutUrl or chargeUrl.
Credit costs (examples): legal-docs 5, hr-docs 4, vat 15, company-reg 40 — full table in GET /catalog or GET /pricing.
Manage billing in the portal at /developer/billing (session) or via /api/v1/billing/* (Bearer).
Rate Limits
Per-key limits depend on your plan tier:
| Plan | Requests/min | Docs/month | Price |
|---|---|---|---|
| free | 10 | 10 | €0 |
| starter | 30 | 100 | €19/mo |
| pro | 100 | 500 | €49/mo |
| enterprise | 300 | 10,000 | €99/mo |
Response headers:
X-RateLimit-Limit: 100
X-RateLimit-Remaining: 87
X-RateLimit-Reset: 1688301540
When exceeded: 429 with { "error": "...", "limit": 100, "window": "1m", "plan": "pro" }.
POST /api/v1/register is additionally limited to 5 requests per 15 minutes per IP.
Error Handling
Errors return flat JSON:
{
"error": "Validation failed",
"details": [{ "path": ["companyName"], "message": "Required" }]
}
| Status | Meaning |
|---|---|
| 400 | Validation error — check details |
| 401 | Missing or invalid API key |
| 402 | Payment required — use checkoutUrl / chargeUrl in response |
| 403 | Insufficient permissions or email not verified |
| 404 | Resource not found |
| 429 | Rate limit exceeded |
| 500 | Internal server error |
Send X-Locale: en for English error messages (default is Bulgarian).
Idempotency
Send Idempotency-Key on mutating /api/v1/* requests to safely retry without duplicate side effects:
curl -X POST https://app.firmify.bg/api/v1/legal-docs \
-H "Authorization: Bearer fmfy_YOUR_API_KEY" \
-H "Idempotency-Key: order-2026-07-14-001" \
-H "Content-Type: application/json" \
-d '{"docType": "GDPR_POLICY"}'
The server caches the response for 24 hours per key + method + path. Replays return the original status and body.
Webhooks
Register an HTTPS endpoint to receive real-time events:
curl -X POST https://app.firmify.bg/api/v1/webhooks \
-H "Authorization: Bearer fmfy_YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{"url": "https://myapp.com/webhook", "events": ["doc.generated", "payment.completed"]}'
Events: payment.completed, doc.generated, doc.failed, job.completed, job.failed
Verification: HMAC-SHA256 of the body in X-Firmify-Signature; event type in X-Firmify-Event.
Management: GET /api/v1/webhooks, DELETE /api/v1/webhooks/{id}
Alternatively, manage webhooks via session at /developer/webhooks when logged into the portal.
Async Jobs
Quick generate (legal/hr/business in one call):
curl -X POST https://app.firmify.bg/api/v1/docs/generate \
-H "Authorization: Bearer fmfy_YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{"docType": "GDPR_POLICY", "params": {"companyName": "Acme OOD", "companyEik": "123456789"}}'
Returns a job ID — poll status:
curl -H "Authorization: Bearer fmfy_YOUR_API_KEY" \
https://app.firmify.bg/api/v1/jobs/{jobId}
Use the full per-category CRUD flow when you need checkout, partial updates, or registry-specific steps. Heavy flows (company registration ZIP, procurement doc-packs) always return a job ID.
Utility Endpoints
| Method | Path | Purpose |
|---|---|---|
| GET | /api/v1/pricing |
Plans, API Pro, credit packs (no auth) |
| GET | /api/v1/catalog |
Products, schemas, credit costs (no auth) |
| POST | /api/v1/register |
Headless signup + first API key |
| POST | /api/v1/estimate |
Price estimate before placing an order |
| POST | /api/v1/batch |
Batch document operations |
| GET | /api/v1/analytics |
Usage analytics for your account |
Registry Workflows
Amendments, share transfers, and dissolution support the full registry pipeline:
- Create order → fill data
POST /{id}/validate— pre-flight validation- Pay (checkout/charge/quota/credits)
POST /{id}/generateGET /{id}/download/{docType}orGET /{id}/zipPOST /{id}/upload-signed-pdf— upload signed PDFPOST /{id}/mark-submitted— mark as submitted to the register
Company registration uses org-scoped paths: /api/v1/company-reg/{orgId}/generate, /zip, /upload-signed, etc.
Procurement
The procurement API covers TED opportunities, saved cases, tasks, and doc-pack generation:
- Profile:
GET/PUT /api/v1/procurement/profile - Opportunities: list, detail, refresh, save, hide
- Cases: CRUD, tasks, notes, AI checklist
- Doc-packs: create, checkout, generate, download
See OpenAPI for the full endpoint list — procurement is the largest surface area.
Accounting API
Separate from public v1 — uses the same API keys with accounting:read or accounting:multi:
GET https://app.firmify.bg/accounting/api/companies
GET https://app.firmify.bg/accounting/api/companies/{id}/invoices
Requires X-Company-Id header (or ?companyId=). Rate limit: 100 req/min per key + company pair.
Sandbox Mode
Sandbox keys (created in the Developer Portal with the sandbox checkbox):
- Skip real Stripe charges and document generation
- Return mock responses for batch and quick-generate flows
- Ideal for CI/CD and integration testing
Use a regular key with the free tier (10 docs/month) for end-to-end testing with real documents.
Code Examples
Node.js
const API_BASE = "https://app.firmify.bg/api/v1";
const API_KEY = process.env.FIRMIFY_API_KEY;
async function generateGdprPolicy(company) {
const headers = {
Authorization: `Bearer ${API_KEY}`,
"Content-Type": "application/json",
"Idempotency-Key": `gdpr-${company.eik}-${Date.now()}`,
};
const createRes = await fetch(`${API_BASE}/legal-docs`, {
method: "POST",
headers,
body: JSON.stringify({ docType: "GDPR_POLICY" }),
});
if (!createRes.ok) {
const err = await createRes.json();
throw new Error(err.error);
}
const { id } = await createRes.json();
const updateRes = await fetch(`${API_BASE}/legal-docs/${id}`, {
method: "PUT",
headers: { ...headers, "Idempotency-Key": undefined },
body: JSON.stringify({ companyName: company.name, companyEik: company.eik }),
});
if (!updateRes.ok) throw new Error((await updateRes.json()).error);
const genRes = await fetch(`${API_BASE}/legal-docs/${id}/generate`, {
method: "POST",
headers: { Authorization: headers.Authorization },
});
if (!genRes.ok) throw new Error((await genRes.json()).error);
return { id, downloadUrl: `${API_BASE}/legal-docs/${id}/download` };
}
Python
import os
import requests
API_BASE = "https://app.firmify.bg/api/v1"
headers = {"Authorization": f"Bearer {os.environ['FIRMIFY_API_KEY']}"}
create = requests.post(
f"{API_BASE}/legal-docs",
json={"docType": "GDPR_POLICY"},
headers={**headers, "Idempotency-Key": "order-001"},
)
create.raise_for_status()
doc_id = create.json()["id"]
update = requests.put(
f"{API_BASE}/legal-docs/{doc_id}",
json={"companyName": "Acme OOD", "companyEik": "123456789"},
headers=headers,
)
update.raise_for_status()
gen = requests.post(f"{API_BASE}/legal-docs/{doc_id}/generate", headers=headers)
gen.raise_for_status()
API Reference
| Resource | URL |
|---|---|
| Scalar (interactive) | https://app.firmify.bg/api/v1/docs |
| Swagger UI | https://app.firmify.bg/api/v1/docs/swagger |
| Redoc | https://app.firmify.bg/api/v1/docs/redoc |
| OpenAPI JSON | https://app.firmify.bg/api/v1/openapi.json |
| OpenAPI YAML | https://app.firmify.bg/api/v1/openapi.yaml |
| Postman collection | https://app.firmify.bg/api/v1/postman.json |
| Developer portal | https://app.firmify.bg/en/developer |
| Tutorial guide | /en/guide/public-api-guide |
| Changelog | /en/guide/api-changelog |
| Support | api@firmify.bg |
Generate a typed client:
npx @openapitools/openapi-generator-cli generate \
-i https://app.firmify.bg/api/v1/openapi.json \
-g typescript-axios \
-o ./firmify-client
All guides
- How to Register a Company in Bulgaria (2026 Guide)
- EOOD vs OOD in Bulgaria — Which to Choose?
- Company Registration Costs in Bulgaria (2026)
- VAT Registration in Bulgaria — When & How
- Corporate Tax in Bulgaria — 10% Flat Rate Guide
- How to Register an EOOD in Bulgaria — Full Guide
- How to Register an OOD in Bulgaria — Full Guide
- Check & Reserve a Company Name in Bulgaria
- Documents for Company Registration in Bulgaria
- Signing Documents for Company Registration — QES, Specimen, Firmify
- Signing Help — Evrotrust, PDF, B-Trust
- Sign PDF with QES Outside Firmify — Adobe Reader, Steps
- Signing with Evrotrust in Firmify — Steps
- B-Trust, Smart Card, and BISS for Signing
- Capital Escrow Account & Company Banking in Bulgaria
- Annual Financial Report (AFR) in Bulgaria — Filing & Deadlines
- Company Registration & Maintenance Costs in Bulgaria
- Online Company Registration in Bulgaria
- Dissolving an EOOD/OOD in Bulgaria — Procedure & Timeline
- Share Transfer in EOOD/OOD — Procedure & Documents
- Company Amendments in the Commercial Register (A15)
- GDPR Policy & Legal Documents for Your Business
- Employment Contract & HR Documents in Bulgaria
- How to Find an Accountant or Lawyer for Your Company in Bulgaria
- Firmify Partner Program — How to Earn
- Business Documents — Quotes, Proforma Invoices, Contracts
- Do I Need a Lawyer to Register a Company in Bulgaria?
- How Long Does Company Registration Take in Bulgaria?
- What to Do After Receiving Your EIK?
- Do I Need a Notary for Company Registration?
- EOOD vs Freelance in Bulgaria — Which Is More Profitable?
- Manager Social Insurance for EOOD — Types & Obligations
- Public procurement radar: CPV codes and your Firmify profile
- What “AI assistance” means for TED notices
- End-to-end Firmify tenders flow: TED radar, case workspace, Stripe document pack
- Paid TED document pack for Firmify tenders: Stripe, statuses, filenames
- How to find the CPV code for your business
- Requirements for participating in public procurement in Bulgaria
- Art. 97a VAT Filing in Bulgaria — Monthly Guide (2026)
- VIES Declaration in Bulgaria — Guide 2026
- How to use the Firmify accounting module
- Monthly accounting checklist for EOOD
- How to file a VAT return with Firmify
- How to record a purchase invoice in Firmify
- How to issue an invoice with Firmify
- How to close an accounting period in Firmify
- Annual financial report (GFO) with Firmify
- Accounting glossary — Bulgaria
- Can an EOOD owner do their own bookkeeping in Bulgaria?
- Bank statement import & reconciliation in Firmify
- Payroll and salaries in Firmify — EOOD guide
- Fixed assets and depreciation in Firmify
- Freelance Profession in Bulgaria — Complete Guide 2026
- BULSTAT Registration for Freelancers — Step by Step 2026
- Taxes and Social Security for Freelancers in Bulgaria 2026
- Freelance vs EOOD Tax Calculator — How to Use
- Freelance Accounting in Bulgaria — Self-Service with Firmify
- Freelance vs EOOD in Bulgaria — When to Incorporate?
- Digital Nomad Tax Guide: Working as a Freelancer in Bulgaria 2026
- How to Register as Self-Employed in Bulgaria as a Foreigner 2026
- VAT for Freelancers in Bulgaria 2026 — When to Register
- OKD-5 Declaration — Registering as Self-Employed in Bulgaria
- Choosing Insurance Income for Freelancers in Bulgaria 2026
- Annual Tax Return (Art. 50) for Freelancers in Bulgaria
- Public API v1 — Developer Documentation
- Using the Firmify Public API — Developer's Guide
- Public API Changelog
Was this article helpful?