Developer Documentation
Build with the Tamar API or use the CLI to tailor resumes from your terminal.
Getting Started
Base URL
https://ask-tamar.com/api/v1Authentication
All API requests require a Bearer token in the Authorization header. API keys start with tmr_ and are 68 characters long.
Authorization: Bearer tmr_your_api_key_hereGetting an API Key
Sign in to your Tamar account, go to Profile β API Keys, and create a new key. The raw key is shown once β store it securely. You can also create keys programmatically via the POST /v1/keys endpoint (authenticated with a session cookie).
Rate Limits
The Developer plan provides 60 requests per minute. Free accounts are limited to 5 requests per minute. Rate-limited responses include Retry-After, X-RateLimit-Limit, and X-RateLimit-Remaining headers.
Error Format
All errors return a consistent JSON structure:
{
"error": {
"code": "ERROR_CODE",
"message": "Human-readable description"
}
}| Code | Meaning |
|---|---|
| UNAUTHORIZED | Missing or invalid API key |
| BAD_REQUEST | Malformed request or missing required fields |
| NOT_FOUND | Resource does not exist or does not belong to you |
| LIMIT_REACHED | Plan generation or iteration limit hit (upgrade required) |
| RATE_LIMITED | Too many requests β slow down |
| UNPROCESSABLE | Request was valid but could not be processed (e.g. unreadable file) |
| INTERNAL_ERROR | Server error β try again later |
Timeouts
Several endpoints involve AI processing and can take 15β60 seconds to respond. Set your HTTP client timeout to at least 120 seconds. Each endpoint below shows its typical response time.
Developer plan β $15/mo
Unlimited resumes, unlimited feedback iterations, and 60 API requests per minute. Built for teams and products integrating Tamar. Get started β
Endpoints
/v1/resumes/parseUpload a PDF, DOCX, or TXT file and extract text content. Max 10 MB.
Request
Multipart form data with a file field.
curl -X POST https://ask-tamar.com/api/v1/resumes/parse \
-H "Authorization: Bearer tmr_..." \
-F "[email protected]"Response 200
{
"data": {
"id": "550e8400-e29b-41d4-a716-446655440000",
"text": "John Doe\nSenior Software Engineer...",
"fileName": "resume.pdf",
"pages": 2
}
}| Status | Code | Description |
|---|---|---|
| 400 | BAD_REQUEST | No file provided or unsupported format |
| 401 | UNAUTHORIZED | Missing or invalid API key |
| 422 | UNPROCESSABLE | Could not extract text from file |
| 429 | RATE_LIMITED | Rate limit exceeded |
/v1/profilesList all experience profiles for the authenticated user, most recently updated first.
Response 200
{
"data": {
"hasProfile": true,
"profiles": [
{
"id": "660e8400-...",
"name": "John Doe",
"currentRole": "Senior Software Engineer",
"seniority": "senior",
"skills": ["Python", "AWS", "PostgreSQL"],
"qaHistoryCount": 4,
"sourceFileName": "resume.pdf",
"updatedAt": "2026-03-28T10:00:00Z"
}
]
}
}Returns hasProfile: false with an empty array if no profile exists yet.
| Status | Code | Description |
|---|---|---|
| 401 | UNAUTHORIZED | Missing or invalid API key |
| 429 | RATE_LIMITED | Rate limit exceeded |
/v1/profiles/questionsGenerate tailored enrichment questions based on a resume. Use the returned questions to collect answers from the candidate, then pass them as qaHistory to POST /profiles/synthesize for a higher-quality experience profile.
Two-step enrichment workflow
- Call
POST /profiles/questionsβ receive questions - Present questions to the user, collect their answers
- Call
POST /profiles/synthesizewithqaHistoryβ create enriched profile
Request
{
"sourceResumeId": "550e8400-..."
}Provide either sourceResumeId or resumeText.
Response 200
{
"data": {
"seniority": "senior",
"questions": [
"Can you tell me about a time you led a cross-functional project?",
"What's the largest team or codebase you've owned end-to-end?"
]
}
}| Status | Code | Description |
|---|---|---|
| 400 | BAD_REQUEST | No resume source provided |
| 401 | UNAUTHORIZED | Missing or invalid API key |
| 404 | NOT_FOUND | Source resume not found |
| 429 | RATE_LIMITED | Rate limit exceeded |
/v1/profiles/synthesizeCreate or update an enriched experience profile from resume text and optional Q&A history. Profiles enable higher-quality tailoring.
Request
{
"sourceResumeId": "550e8400-...",
"resumeText": "John Doe\nSenior Software Engineer...",
"qaHistory": [
{ "q": "Tell me about your leadership experience", "a": "I led a team of 5..." }
]
}Provide either sourceResumeId or resumeText. The qaHistory is optional.
Response 200
{
"data": {
"profileId": "660e8400-...",
"profileData": { "...": "structured experience profile" },
"seniority": "senior"
}
}| Status | Code | Description |
|---|---|---|
| 400 | BAD_REQUEST | Missing resume text |
| 401 | UNAUTHORIZED | Missing or invalid API key |
| 404 | NOT_FOUND | Source resume not found |
/v1/tailorGenerate a tailored resume by matching a resume or profile against a job description. Resolution order: profileId β sourceResumeId β resumeText.
Request
{
"profileId": "660e8400-...",
"sourceResumeId": "550e8400-...",
"resumeText": "John Doe\nSenior Software Engineer...",
"jobDescription": "We are looking for a Senior Backend Engineer...",
"jobUrl": "https://linkedin.com/jobs/12345"
}Provide at least one of profileId, sourceResumeId, or resumeText. Provide either jobDescription or jobUrl.
cURL Example
curl -X POST https://ask-tamar.com/api/v1/tailor \
-H "Authorization: Bearer tmr_..." \
-H "Content-Type: application/json" \
-d '{
"sourceResumeId": "550e8400-...",
"jobDescription": "Senior Backend Engineer at Acme Corp..."
}'Response 200
{
"data": {
"id": "770e8400-...",
"quality": "enriched",
"analysis": {
"mustHaves": ["Python", "AWS", "PostgreSQL"],
"niceToHaves": ["Kubernetes", "GraphQL"],
"strongMatches": ["Python", "AWS"],
"transferableSkills": ["System design β architecture role"],
"gaps": ["Kubernetes β no production experience listed"]
},
"resumeData": { "...": "structured resume data" },
"changes": [{ "...": "list of changes made" }]
}
}The quality field is either enriched (used an experience profile) or basic (resume text only).
| Status | Code | Description |
|---|---|---|
| 400 | BAD_REQUEST | Missing required fields |
| 401 | UNAUTHORIZED | Missing or invalid API key |
| 402 | LIMIT_REACHED | Resume generation limit reached |
| 422 | UNPROCESSABLE | Could not fetch or parse jobUrl (site may block access β paste text instead) |
| 429 | RATE_LIMITED | Rate limit exceeded |
/v1/tailor/:idFetch a generated resume by ID. Optionally specify a version number.
Query Parameters
| Param | Type | Description |
|---|---|---|
| version | integer | Specific version number (optional) |
Response 200
{
"data": {
"id": "770e8400-...",
"resumeData": { "...": "structured resume" },
"analysis": { "...": "match analysis" },
"changes": [{ "...": "changes" }],
"version": 2,
"versions": [
{ "id": "770e8400-...", "version": 1, "feedback": null, "created_at": "2026-03-28T10:00:00Z" },
{ "id": "770e8400-...", "version": 2, "feedback": "Make the summary shorter", "created_at": "2026-03-28T10:05:00Z" }
],
"quality": "enriched"
}
}| Status | Code | Description |
|---|---|---|
| 401 | UNAUTHORIZED | Missing or invalid API key |
| 404 | NOT_FOUND | Resume not found |
/v1/tailor/:id/feedbackSubmit natural-language feedback to modify a generated resume. Creates a new version.
Request
{
"feedback": "Make the summary shorter and emphasize Python experience",
"selectionContext": { "optional": "context about selected elements" }
}Response 200
{
"data": {
"version": 3,
"changes": [{ "...": "changes applied" }],
"resumeData": { "...": "updated structured resume" }
}
}| Status | Code | Description |
|---|---|---|
| 400 | BAD_REQUEST | Missing feedback or resume is finalised |
| 401 | UNAUTHORIZED | Missing or invalid API key |
| 402 | LIMIT_REACHED | Iteration limit reached |
| 404 | NOT_FOUND | Resume not found |
/v1/tailor/:id/pdfGenerate and download a PDF of the tailored resume.
cURL Example
curl -o resume.pdf \
https://ask-tamar.com/api/v1/tailor/770e8400-.../pdf \
-H "Authorization: Bearer tmr_..."Response 200
Binary PDF file with Content-Type: application/pdf.
| Status | Code | Description |
|---|---|---|
| 400 | BAD_REQUEST | Resume has no structured data yet |
| 401 | UNAUTHORIZED | Missing or invalid API key |
| 404 | NOT_FOUND | Resume not found |
/v1/accountCheck your current plan, subscription status, and usage for the billing period.
Response 200
{
"data": {
"plan": "developer",
"planLabel": "Developer",
"subscriptionStatus": "active",
"currentPeriodEnd": "2026-04-28T10:00:00Z",
"canceling": false,
"usage": {
"resumesThisMonth": 7,
"resumeLimit": null,
"iterationsPerResume": null
},
"rateLimitPerMinute": 60
}
}resumeLimit and iterationsPerResume are null when unlimited.
| Status | Code | Description |
|---|---|---|
| 401 | UNAUTHORIZED | Missing or invalid API key |
| 429 | RATE_LIMITED | Rate limit exceeded |
/v1/keysCreate a new API key. Requires a session cookie (not an API key) β use this from the web app or after logging in. The raw key is returned once. Store it securely.
Request
{
"name": "My integration"
}Response 201
{
"data": {
"id": "880e8400-...",
"key": "tmr_abc123def456...",
"keyPrefix": "tmr_abc1...",
"name": "My integration",
"plan": "free",
"createdAt": "2026-03-28T10:00:00Z"
}
}The key field is only shown in this response. Subsequent listings show only the prefix.
| Status | Code | Description |
|---|---|---|
| 400 | BAD_REQUEST | Too many keys or invalid name |
| 401 | UNAUTHORIZED | Session required |
/v1/keysList your API keys. Requires a session cookie.
Response 200
{
"data": [
{
"id": "880e8400-...",
"keyPrefix": "tmr_abc1...",
"name": "My integration",
"plan": "free",
"scopes": ["*"],
"lastUsedAt": "2026-03-28T12:00:00Z",
"expiresAt": null,
"revoked": false,
"createdAt": "2026-03-28T10:00:00Z"
}
]
}| Status | Code | Description |
|---|---|---|
| 401 | UNAUTHORIZED | Session required |
/v1/keys/:idRevoke an API key. This is a soft-delete and cannot be undone. Requires a session cookie.
Response 200
{
"data": {
"id": "880e8400-...",
"revoked": true
}
}| Status | Code | Description |
|---|---|---|
| 401 | UNAUTHORIZED | Session required |
| 404 | NOT_FOUND | Key not found or already revoked |