gitmd2pdf Powered by AgenticMods.ai

API Documentation

gitmd2pdf provides a RESTful API for programmatic markdown-to-PDF conversion. The API supports two authentication methods: HMAC-SHA256 for direct request signing, and OAuth 2.0 client credentials for token-based access. Both support asynchronous job processing with optional webhooks.

Authentication

You'll need a Premium subscription to access the API. Two authentication methods are available:

Method Best For Token Management
HMAC Server-to-server, CI/CD, scripts No tokens - sign each request
OAuth Long-running sessions, SDKs Obtain token, use for 1 hour

Getting API Credentials

  1. Log in to your gitmd2pdf account
  2. Navigate to Account > API Keys
  3. Click Generate New Credentials
  4. Select credential type: HMAC or OAuth
  5. Save your client_id and client_secret securely (secret shown only once)

Auth-by-endpoint summary

The server accepts different auth schemes on different endpoint families. Use this table when wiring an integration — the Authentication: line on each endpoint below repeats the same information:

Endpoint(s) Accepted auth schemes
POST /v1/oauth/token Public (no auth header) — exchange client_id + client_secret for a 1-hour OAuth bearer token
POST /v1/convert HMAC or OAuth Bearer
GET /v1/jobs/:jobId/status HMAC or OAuth Bearer
GET /v1/jobs/:jobId/download HMAC or OAuth Bearer
POST / GET / DELETE /v1/callback-allowlist[/...] HMAC or OAuth Bearer (for API customers) — server also accepts Session JWT here, but that's an internal SPA path
POST / GET / DELETE /v1/credentials[/...] Session JWT only (must use the Dashboard / Convert to PDF UI — these endpoints mint and revoke API credentials and are not callable via API auth by design)

For API customers, the auth model is simple: use HMAC or OAuth Bearer on every endpoint that's not /v1/credentials. The "Session JWT" entries in the table above are an internal SPA implementation detail and not part of the API surface you'll integrate against.

"Session JWT" is the token the browser holds after logging into the SPA at gitmd2pdf.com; it carries {userId, isAdmin, …} and is signed with JWT_SECRET. It is completely separate from OAuth bearer tokens (which are opaque random tokens issued by /v1/oauth/token). The server distinguishes the two by token shape: JWTs have three dot-separated segments; OAuth tokens do not. Two endpoint families pass session JWTs through to their handlers so the SPA can call them on behalf of the logged-in user: /v1/callback-allowlist (the Convert to PDF page's "Callback domains" UI panel) and /v1/credentials (the API Keys management UI). For programmatic API integration neither of these paths is relevant — use HMAC or OAuth Bearer.

Conversion endpoints (/v1/convert, /v1/jobs/:jobId/status, /v1/jobs/:jobId/download) do NOT accept session JWTs at all — HMAC or OAuth Bearer only, regardless of caller.


HMAC Authentication

HMAC authentication signs each request directly using your client secret. No token exchange required.

Request Headers

Header Description
Authorization HMAC <client_id>:<signature>
X-Timestamp Unix timestamp in milliseconds

Computing the Signature

The signature is computed as follows:

signing_string = METHOD + PATH + TIMESTAMP + BODY
secret_hash = SHA256(client_secret)
signature = HMAC-SHA256(secret_hash, signing_string)

Example (JavaScript/Node.js):

const crypto = require('crypto');

function signRequest({ clientId, clientSecret, method, path, body, timestamp }) {
  // Hash the client secret
  const secretHash = crypto.createHash('sha256')
    .update(clientSecret)
    .digest('hex');

  // Build signing string: METHOD + PATH + TIMESTAMP + BODY
  const signingString = `${method}${path}${timestamp}${body}`;

  // Compute HMAC-SHA256 signature
  const signature = crypto.createHmac('sha256', secretHash)
    .update(signingString)
    .digest('hex');

  return `HMAC ${clientId}:${signature}`;
}

// Usage
const timestamp = String(Date.now());
const body = JSON.stringify({ type: 'repo', repoUrl: 'https://github.com/user/repo' });
const authorization = signRequest({
  clientId: 'your_client_id',
  clientSecret: 'your_client_secret',
  method: 'POST',
  path: '/api/v1/convert',
  body,
  timestamp
});

// Make request
fetch('https://gitmd2pdf.com/api/v1/convert', {
  method: 'POST',
  headers: {
    'Authorization': authorization,
    'X-Timestamp': timestamp,
    'Content-Type': 'application/json'
  },
  body
});

Example (Python):

import hashlib
import hmac
import time
import json
import requests

def sign_request(client_id, client_secret, method, path, body, timestamp):
    # Hash the client secret
    secret_hash = hashlib.sha256(client_secret.encode()).hexdigest()

    # Build signing string
    signing_string = f"{method}{path}{timestamp}{body}"

    # Compute HMAC-SHA256 signature
    signature = hmac.new(
        secret_hash.encode(),
        signing_string.encode(),
        hashlib.sha256
    ).hexdigest()

    return f"HMAC {client_id}:{signature}"

# Usage
timestamp = str(int(time.time() * 1000))
body = json.dumps({"type": "repo", "repoUrl": "https://github.com/user/repo"})
authorization = sign_request(
    client_id="your_client_id",
    client_secret="your_client_secret",
    method="POST",
    path="/api/v1/convert",
    body=body,
    timestamp=timestamp
)

response = requests.post(
    "https://gitmd2pdf.com/api/v1/convert",
    headers={
        "Authorization": authorization,
        "X-Timestamp": timestamp,
        "Content-Type": "application/json"
    },
    data=body
)

Example (Bash/curl):

#!/bin/bash
CLIENT_ID="your_client_id"
CLIENT_SECRET="your_client_secret"
TIMESTAMP=$(date +%s000)
METHOD="POST"
PATH="/api/v1/convert"
BODY='{"type":"repo","repoUrl":"https://github.com/user/repo"}'

# Hash the secret
SECRET_HASH=$(echo -n "$CLIENT_SECRET" | sha256sum | cut -d' ' -f1)

# Build signing string and compute signature
SIGNING_STRING="${METHOD}${PATH}${TIMESTAMP}${BODY}"
SIGNATURE=$(echo -n "$SIGNING_STRING" | openssl dgst -sha256 -hmac "$SECRET_HASH" | cut -d' ' -f2)

curl -X POST "https://gitmd2pdf.com${PATH}" \
  -H "Authorization: HMAC ${CLIENT_ID}:${SIGNATURE}" \
  -H "X-Timestamp: ${TIMESTAMP}" \
  -H "Content-Type: application/json" \
  -d "$BODY"

Replay Protection

Timestamps must be within 5 minutes of server time. Requests with stale or future timestamps will be rejected.


OAuth Authentication

OAuth 2.0 client credentials flow for obtaining bearer tokens.

Obtaining an Access Token

curl -X POST https://gitmd2pdf.com/api/v1/oauth/token \
  -H "Content-Type: application/json" \
  -d '{
    "grant_type": "client_credentials",
    "client_id": "your_client_id",
    "client_secret": "your_client_secret"
  }'

Response:

{
  "access_token": "eyJhbGciOiJIUzI1NiIs...",
  "token_type": "Bearer",
  "expires_in": 3600
}

Use the access token in subsequent requests:

Authorization: Bearer <access_token>

Tokens expire after 1 hour. Request a new token when expired.


Endpoints

POST /api/v1/convert

Enqueue a markdown-to-PDF conversion job.

Authentication: Required — HMAC or OAuth Bearer. Session JWTs are not accepted on this endpoint. See the Auth-by-endpoint summary above.

Content-Type: application/json or multipart/form-data (for file uploads)

Request Body

Parameter Type Required Description
type string Yes Conversion type: file, url, or repo
url string For url type URL to a markdown file
repoUrl string For repo type Git repository URL
branch string No Branch name (default: main/master)
title string No Custom title for the PDF (max 256 chars)
callbackUrl string No Webhook URL for job completion notification
callbackAuthHeader string No Authorization header for callback requests
repoCredentials object No Per-request HTTPS git credentials for type=repo (private repositories). Object with username (non-empty string) and token (non-empty string, max 512 characters). Single-use — never stored, never logged. Ignored for non-repo types.

Example: Convert a File

curl -X POST https://gitmd2pdf.com/api/v1/convert \
  -H "Authorization: Bearer <access_token>" \
  -F "type=file" \
  -F "file=@README.md" \
  -F "title=My Documentation"

Example: Convert a Repository

curl -X POST https://gitmd2pdf.com/api/v1/convert \
  -H "Authorization: Bearer <access_token>" \
  -H "Content-Type: application/json" \
  -d '{
    "type": "repo",
    "repoUrl": "https://github.com/user/repo",
    "branch": "main",
    "title": "Project Documentation"
  }'

Example: Convert a URL

curl -X POST https://gitmd2pdf.com/api/v1/convert \
  -H "Authorization: Bearer <access_token>" \
  -H "Content-Type: application/json" \
  -d '{
    "type": "url",
    "url": "https://raw.githubusercontent.com/user/repo/main/README.md"
  }'

Example: Convert a Private Repository

Pass a single-use HTTPS credential pair via repoCredentials. The token is forwarded only to the git clone step (via subprocess environment) and is never persisted or logged.

curl -X POST https://gitmd2pdf.com/api/v1/convert \
  -H "Authorization: Bearer <access_token>" \
  -H "Content-Type: application/json" \
  -d '{
    "type": "repo",
    "repoUrl": "https://github.com/your-org/private-repo",
    "repoCredentials": {
      "username": "your-github-username",
      "token": "ghp_xxxxxxxxxxxxxxxxxxxx"
    }
  }'

Response (202 Accepted):

{
  "jobId": "job_abc123def456",
  "status": "queued"
}

When callbackUrl is provided, the response also includes a callbackSigningSecret you must keep to verify webhook signatures. The secret is returned exactly once — at job creation time — and is not retrievable afterwards.

{
  "jobId": "job_abc123def456",
  "status": "queued",
  "callbackSigningSecret": "cbk_secret_XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"
}

GET /api/v1/jobs/:jobId/status

Check the status of a conversion job.

Authentication: Required — HMAC or OAuth Bearer. Session JWTs are not accepted.

Response:

{
  "jobId": "job_abc123def456",
  "status": "completed",
  "createdAt": "2024-01-15T10:30:00Z",
  "updatedAt": "2024-01-15T10:30:15Z",
  "downloadUrl": "/api/v1/jobs/job_abc123def456/download"
}

Status Values:

Response Fields:

Field Type When Present Description
jobId string always The job identifier
status string always One of queued, processing, completed, failed
createdAt ISO 8601 always Job enqueue timestamp
updatedAt ISO 8601 always Last status-change timestamp (equals completion time when status=completed)
downloadUrl string status=completed and no callback was used Relative URL to download the PDF (one-shot)
error string status=failed Failure reason
message string status=completed and PDF was already delivered to a callback URL Human-readable note that the file is no longer available for download
callbackRequested bool callback URL was provided on the original request Always true in that case
callbackDelivered bool callback URL was provided Whether the callback POST succeeded
callbackDeliveredAt ISO 8601 callbackDelivered=true Successful-delivery timestamp
callbackLastAttemptAt ISO 8601 a delivery attempt has occurred Timestamp of the most recent attempt (success or failure)
callbackError string callback delivery failed after retries Last error message
config_applied bool type=repo job reached status=completed (jobs from change-290 onward) true if .gitmd2pdf.yml was found at repo root AND the caller was Premium
structure_applied bool as above true if config_applied is true AND the config's structure: key produced a non-empty node list

GET /api/v1/jobs/:jobId/download

Download the converted PDF file.

Authentication: Required — HMAC or OAuth Bearer. Session JWTs are not accepted.

Response: Binary PDF file with Content-Type: application/pdf

curl -X GET https://gitmd2pdf.com/api/v1/jobs/job_abc123def456/download \
  -H "Authorization: Bearer <access_token>" \
  -o output.pdf

Webhooks

For asynchronous workflows, configure a callback URL to receive job completion notifications.

Setting Up Webhooks

  1. Add callback domain to allowlist:
curl -X POST https://gitmd2pdf.com/api/v1/callback-allowlist \
  -H "Authorization: Bearer <access_token>" \
  -H "Content-Type: application/json" \
  -d '{"domain": "api.yourapp.com"}'
  1. Include callback URL in conversion request:
curl -X POST https://gitmd2pdf.com/api/v1/convert \
  -H "Authorization: Bearer <access_token>" \
  -H "Content-Type: application/json" \
  -d '{
    "type": "repo",
    "repoUrl": "https://github.com/user/repo",
    "callbackUrl": "https://api.yourapp.com/webhooks/gitmd2pdf",
    "callbackAuthHeader": "Bearer your-webhook-secret"
  }'

Callback Authentication (callbackAuthHeader) — Bring-Your-Own Credential

gitmd2pdf does not issue, validate, store, or rotate the callback auth credential. It is pure pass-through: the string you supply as callbackAuthHeader is relayed verbatim as the X-Auth header (mirrored as x-apikey) on the inbound callback POST to your receiver. There is no "get the token" step on the gitmd2pdf side — you invent the credential, register it on your own receiver, and tell gitmd2pdf to send it back.

End-to-end flow for a generic customer running their own callback receiver:

  1. Stand up an HTTPS endpoint on your own infrastructure (e.g. https://acme.com/pdf-intake).
  2. Implement whatever authentication scheme you want on it — a static bearer token you generate, an HMAC scheme, an API gateway key, mTLS client cert, etc. gitmd2pdf never sees or evaluates this credential.
  3. Allowlist your domain (gitmd2pdf.com → Convert to PDF → Callback domains → add acme.com, or POST /api/v1/callback-allowlist). Exact-hostname match, no wildcards.
  4. When calling POST /api/v1/convert, include your credential as callbackAuthHeader: "<whatever-you-chose>" (max 512 chars).
  5. gitmd2pdf relays that string verbatim as the X-Auth header on the inbound callback POST. Your receiver validates it however it likes.

The callbackAuthHeader field exists precisely so gitmd2pdf does not have to mint and exchange credentials on your behalf — it is a string-relay for callback auth, nothing more.

callbackAuthHeader vs callbackSigningSecret — don't confuse the two:

Field Who mints it Purpose
callbackAuthHeader You (any string, any scheme) Authenticates gitmd2pdf to your receiver via the X-Auth header
callbackSigningSecret gitmd2pdf (returned once at job creation) Authenticates the PDF body itself via HMAC-SHA256 in X-Signature so you can detect tampering

Webhook Delivery

When the job completes, gitmd2pdf POSTs the PDF binary directly to your callbackUrl (the body is the PDF bytes — there is no JSON envelope, and no separate /download step is needed).

Request line and headers:

POST <your callbackUrl>
Content-Type: application/pdf
Content-Length: <byte length of PDF>
X-Job-ID: job_abc123def456
X-Delivery-Attempt: 1
X-Signature: sha256=<hex-hmac of body, keyed by callbackSigningSecret>
X-Auth: <your callbackAuthHeader, if provided>
x-apikey: <same value as X-Auth — sent for receivers such as RestDB that expect the key in `x-apikey`>

Body: the PDF file bytes.

Verification. Compute HMAC-SHA256(callbackSigningSecret, body) and compare to the value after sha256= in the X-Signature header. The callbackSigningSecret is returned exactly once at job creation.

Retry policy. Up to 3 attempts (0s, 1s, 2s backoff) with a 30s per-attempt timeout. After successful delivery the PDF is deleted server-side — there is no download fallback for callback-mode jobs.

To inspect delivery status (success, retry count, error), poll GET /api/v1/jobs/:jobId/status; the response includes callbackDelivered, callbackDeliveredAt, callbackLastAttemptAt, and callbackError fields.

Managing Callback Allowlist

Authentication: Required — HMAC or OAuth Bearer, same as every other API endpoint. Caller must be on a Premium account (admins bypass the Premium check).

Implementation note: the server's sessionOrApiAuth middleware on these endpoints also accepts the Dashboard session JWT, which is what the Convert to PDF page in the SPA uses internally when a logged-in user manages their allowlist via the UI panel. API customers do not need to think about this — use HMAC or OAuth Bearer.

Add a domain:

POST /api/v1/callback-allowlist
Content-Type: application/json

{ "domain": "api.yourapp.com" }

List allowed domains:

GET /api/v1/callback-allowlist

Remove a domain:

DELETE /api/v1/callback-allowlist/:domain

API Credentials Management

These endpoints mint, list, and revoke the HMAC / OAuth credentials you use to call every other API endpoint. They are Session JWT only by design — they are not callable via HMAC or OAuth Bearer. The intended path is via the Dashboard / Convert to PDF UI (which is what calls these endpoints behind the scenes). If you want to automate credential rotation, you would do so through a session-authenticated browser flow, not through the API itself.

POST /api/v1/credentials

Generate new API credentials.

Authentication: Required — Session JWT only.

GET /api/v1/credentials

List your API credentials.

Authentication: Required — Session JWT only.

DELETE /api/v1/credentials/:clientId

Revoke API credentials.

Authentication: Required — Session JWT only.


Rate Limits

API requests are throttled per credential using a token-bucket. The bucket has a capacity of 10 tokens and refills over 60 seconds, so sustained usage is bounded at roughly 10 requests per minute with short bursts up to 10.

When the bucket is empty, the server returns 429 Too Many Requests with a Retry-After header (seconds to wait):

HTTP/1.1 429 Too Many Requests
Retry-After: 6
Content-Type: application/json

{ "error": "Rate limit exceeded. Try again later." }

X-RateLimit-Limit / X-RateLimit-Remaining / X-RateLimit-Reset headers are not currently emitted — clients should rely on Retry-After on 429 responses.


Error Responses

All errors follow a consistent format:

{
  "error": "Error message describing the issue"
}
Status Code Description
400 Bad Request - Invalid parameters
401 Unauthorized - Invalid or expired token
403 Forbidden - Insufficient permissions or plan
404 Not Found - Resource doesn't exist
429 Too Many Requests - Rate limit exceeded
500 Internal Server Error
503 Service Unavailable - Try again later

OpenAPI Specification

The complete OpenAPI 3.0 specification is available at:

GET /api/v1/openapi.json

You can import this into tools like Postman, Insomnia, or Swagger UI for interactive API exploration.


Code Examples

Python

import requests

# Get access token
auth_response = requests.post(
    "https://gitmd2pdf.com/api/v1/oauth/token",
    json={
        "grant_type": "client_credentials",
        "client_id": "your_client_id",
        "client_secret": "your_client_secret"
    }
)
token = auth_response.json()["access_token"]

# Convert a repository
headers = {"Authorization": f"Bearer {token}"}
convert_response = requests.post(
    "https://gitmd2pdf.com/api/v1/convert",
    headers=headers,
    json={
        "type": "repo",
        "repoUrl": "https://github.com/user/repo"
    }
)
job_id = convert_response.json()["jobId"]

# Poll for completion
import time
while True:
    status = requests.get(
        f"https://gitmd2pdf.com/api/v1/jobs/{job_id}/status",
        headers=headers
    ).json()
    if status["status"] == "completed":
        break
    time.sleep(2)

# Download PDF
pdf = requests.get(
    f"https://gitmd2pdf.com/api/v1/jobs/{job_id}/download",
    headers=headers
)
with open("output.pdf", "wb") as f:
    f.write(pdf.content)

Node.js

const axios = require('axios');
const fs = require('fs');

async function convertRepo(repoUrl) {
  // Get access token
  const { data: auth } = await axios.post(
    'https://gitmd2pdf.com/api/v1/oauth/token',
    {
      grant_type: 'client_credentials',
      client_id: process.env.GITMD2PDF_CLIENT_ID,
      client_secret: process.env.GITMD2PDF_CLIENT_SECRET
    }
  );

  const headers = { Authorization: `Bearer ${auth.access_token}` };

  // Start conversion
  const { data: job } = await axios.post(
    'https://gitmd2pdf.com/api/v1/convert',
    { type: 'repo', repoUrl },
    { headers }
  );

  // Poll for completion
  let status;
  do {
    await new Promise(r => setTimeout(r, 2000));
    const { data } = await axios.get(
      `https://gitmd2pdf.com/api/v1/jobs/${job.jobId}/status`,
      { headers }
    );
    status = data.status;
  } while (status !== 'completed' && status !== 'failed');

  // Download PDF
  const pdf = await axios.get(
    `https://gitmd2pdf.com/api/v1/jobs/${job.jobId}/download`,
    { headers, responseType: 'arraybuffer' }
  );

  fs.writeFileSync('output.pdf', pdf.data);
}

convertRepo('https://github.com/user/repo');

Support

For API support, contact us at support@mods-software.com or visit our FAQ.